use proc_macro::TokenStream;
use quote::quote;
use syn::{ItemFn, Visibility, parse_macro_input};
const EXPECTED_ENTRY_SIGNATURE: &str = r#"
The application entry should have the following signature:
#[rustolio::entry]
pub async fn main() {
// ...
}
"#;
#[proc_macro_attribute]
pub fn entry(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
if let Err(e) = check(&input_fn) {
return e;
}
let web_entry = quote! {
#[cfg(target_arch = "wasm32")]
#[doc(hidden)]
mod wasm_entry {
use super::*;
use __core_macros::wasm_bindgen;
use __core_macros::wasm_bindgen::prelude::*;
use __core_macros::wasm_bindgen_futures;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(js_name = entry)]
#input_fn
}
};
let server_main = quote! {
#[cfg(not(target_arch = "wasm32"))]
#[doc(hidden)]
mod non_wasm_main {
use super::*;
use __core_macros::tokio;
#[tokio::main]
#[allow(unused)]
#input_fn
}
#[cfg(not(target_arch = "wasm32"))]
pub use non_wasm_main::main;
};
let expanded = quote! {
#web_entry
#server_main
};
expanded.into()
}
fn check(input_fn: &ItemFn) -> Result<(), TokenStream> {
if !matches!(input_fn.vis, Visibility::Public(_)) {
return Err(syn::Error::new_spanned(
input_fn.sig.clone(),
format!(
"Expected function to be public: {}",
EXPECTED_ENTRY_SIGNATURE
),
)
.to_compile_error()
.into());
};
if input_fn.sig.asyncness.is_none() {
return Err(syn::Error::new_spanned(
input_fn.sig.clone(),
format!(
"Expected function to be async: {}",
EXPECTED_ENTRY_SIGNATURE
),
)
.to_compile_error()
.into());
};
if input_fn.sig.ident != "main" {
return Err(syn::Error::new_spanned(
input_fn.sig.clone(),
format!(
"Expected function to be named `main`: {}",
EXPECTED_ENTRY_SIGNATURE
),
)
.to_compile_error()
.into());
}
if !input_fn.sig.generics.params.is_empty() {
return Err(syn::Error::new_spanned(
input_fn.sig.clone(),
format!(
"Expected function to have no generics: {}",
EXPECTED_ENTRY_SIGNATURE
),
)
.to_compile_error()
.into());
}
Ok(())
}