rustolio-core-macros 0.1.0

The core macros in the rustolio framework
Documentation
//
// SPDX-License-Identifier: MPL-2.0
//
// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//

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;

            // Create an entry function for the web
            #[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(())
}

// #[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 fn_body = &input_fn.block;

//     let web_entry = quote! {
//         #[wasm_bindgen]
//         pub async fn entry() {
//             #fn_body
//         }
//     };

//     web_entry.into()
// }