cotis-macros 0.1.0-alpha

Modular Rust UI framework core
Documentation
//! Proc-macro attributes that register application entry points for `cotis::launch`.
//!
//! Use these when a renderer or runtime owns startup and calls
//! `cotis::launch::cotis_launch` or `cotis::launch::cotis_launch_async` after
//! its own initialization.

use proc_macro::TokenStream;
use quote::quote;
use syn::{ItemFn, parse_macro_input};

/// Registers a synchronous application entry point for platform-controlled launch.
///
/// The annotated function must take no parameters and must not be `async`.
/// Expands to an `extern "Rust"` `__cotis_launch_hook` symbol consumed by
/// `cotis::launch::cotis_launch`.
///
/// # Examples
///
/// ```ignore
/// use cotis_macros::cotis_start;
///
/// #[cotis_start]
/// fn start() {
///     // Application setup and UI loop.
/// }
/// ```
#[proc_macro_attribute]
pub fn cotis_start(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input_fn = parse_macro_input!(item as ItemFn);

    if !input_fn.sig.inputs.is_empty() {
        return syn::Error::new_spanned(
            &input_fn.sig,
            "#[cotis_start] functions must not take parameters",
        )
        .to_compile_error()
        .into();
    }

    if input_fn.sig.asyncness.is_some() {
        return syn::Error::new_spanned(
            &input_fn.sig,
            "#[cotis_start] cannot be applied to async functions. Use #[cotis_start_async] instead.",
        )
        .to_compile_error()
        .into();
    }

    let fn_name = &input_fn.sig.ident;

    let expanded = quote! {
        #input_fn

        #[doc(hidden)]
        #[unsafe(no_mangle)]
        pub extern "Rust" fn __cotis_launch_hook() {
            #fn_name();
        }
    };

    expanded.into()
}

/// Registers an asynchronous application entry point for platform-controlled launch.
///
/// The annotated function must take no parameters. It may be `async` or return
/// immediately; either way it is boxed into the future awaited by
/// `cotis::launch::cotis_launch_async`.
///
/// # Examples
///
/// ```ignore
/// use cotis_macros::cotis_start_async;
///
/// #[cotis_start_async]
/// async fn start() {
///     // Async application setup and UI loop.
/// }
/// ```
#[proc_macro_attribute]
pub fn cotis_start_async(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input_fn = parse_macro_input!(item as ItemFn);

    if !input_fn.sig.inputs.is_empty() {
        return syn::Error::new_spanned(
            &input_fn.sig,
            "#[cotis_start_async] functions must not take parameters",
        )
        .to_compile_error()
        .into();
    }

    let fn_name = &input_fn.sig.ident;
    let hook_body = if input_fn.sig.asyncness.is_some() {
        quote! {
            Box::new(#fn_name())
        }
    } else {
        quote! {
            Box::new(async move {
                #fn_name();
            })
        }
    };

    let expanded = quote! {
        #input_fn

        #[doc(hidden)]
        #[unsafe(no_mangle)]
        pub extern "Rust" fn __cotis_launch_async_hook() -> Box<dyn ::core::future::Future<Output = ()>> {
            #hook_body
        }
    };

    expanded.into()
}