Skip to main content

rustolio_core_macros/
lib.rs

1//
2// SPDX-License-Identifier: MPL-2.0
3//
4// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at http://mozilla.org/MPL/2.0/.
9//
10
11use proc_macro::TokenStream;
12use quote::quote;
13use syn::{ItemFn, Visibility, parse_macro_input};
14
15const EXPECTED_ENTRY_SIGNATURE: &str = r#"
16The application entry should have the following signature:
17
18#[rustolio::entry]
19pub async fn main() {
20    // ...
21}
22"#;
23
24#[proc_macro_attribute]
25pub fn entry(_attr: TokenStream, item: TokenStream) -> TokenStream {
26    let input_fn = parse_macro_input!(item as ItemFn);
27
28    if let Err(e) = check(&input_fn) {
29        return e;
30    }
31
32    let web_entry = quote! {
33        #[cfg(target_arch = "wasm32")]
34        #[doc(hidden)]
35        mod wasm_entry {
36            use super::*;
37            use __core_macros::wasm_bindgen;
38            use __core_macros::wasm_bindgen::prelude::*;
39            use __core_macros::wasm_bindgen_futures;
40
41            // Create an entry function for the web
42            #[cfg(target_arch = "wasm32")]
43            #[wasm_bindgen(js_name = entry)]
44            #input_fn
45        }
46    };
47
48    let server_main = quote! {
49        #[cfg(not(target_arch = "wasm32"))]
50        #[doc(hidden)]
51        mod non_wasm_main {
52            use super::*;
53            use __core_macros::tokio;
54
55            #[tokio::main]
56            #[allow(unused)]
57            #input_fn
58        }
59        #[cfg(not(target_arch = "wasm32"))]
60        pub use non_wasm_main::main;
61    };
62
63    let expanded = quote! {
64        #web_entry
65        #server_main
66    };
67
68    expanded.into()
69}
70
71fn check(input_fn: &ItemFn) -> Result<(), TokenStream> {
72    if !matches!(input_fn.vis, Visibility::Public(_)) {
73        return Err(syn::Error::new_spanned(
74            input_fn.sig.clone(),
75            format!(
76                "Expected function to be public: {}",
77                EXPECTED_ENTRY_SIGNATURE
78            ),
79        )
80        .to_compile_error()
81        .into());
82    };
83
84    if input_fn.sig.asyncness.is_none() {
85        return Err(syn::Error::new_spanned(
86            input_fn.sig.clone(),
87            format!(
88                "Expected function to be async: {}",
89                EXPECTED_ENTRY_SIGNATURE
90            ),
91        )
92        .to_compile_error()
93        .into());
94    };
95
96    if input_fn.sig.ident != "main" {
97        return Err(syn::Error::new_spanned(
98            input_fn.sig.clone(),
99            format!(
100                "Expected function to be named `main`: {}",
101                EXPECTED_ENTRY_SIGNATURE
102            ),
103        )
104        .to_compile_error()
105        .into());
106    }
107
108    if !input_fn.sig.generics.params.is_empty() {
109        return Err(syn::Error::new_spanned(
110            input_fn.sig.clone(),
111            format!(
112                "Expected function to have no generics: {}",
113                EXPECTED_ENTRY_SIGNATURE
114            ),
115        )
116        .to_compile_error()
117        .into());
118    }
119
120    Ok(())
121}
122
123// #[proc_macro_attribute]
124// pub fn entry(_attr: TokenStream, item: TokenStream) -> TokenStream {
125//     let input_fn = parse_macro_input!(item as ItemFn);
126
127//     if let Err(e) = check(&input_fn) {
128//         return e;
129//     }
130
131//     let fn_body = &input_fn.block;
132
133//     let web_entry = quote! {
134//         #[wasm_bindgen]
135//         pub async fn entry() {
136//             #fn_body
137//         }
138//     };
139
140//     web_entry.into()
141// }