Skip to main content

cotis_macros/
lib.rs

1//! Proc-macro attributes that register application entry points for `cotis::launch`.
2//!
3//! Use these when a renderer or runtime owns startup and calls
4//! `cotis::launch::cotis_launch` or `cotis::launch::cotis_launch_async` after
5//! its own initialization.
6
7use proc_macro::TokenStream;
8use quote::quote;
9use syn::{ItemFn, parse_macro_input};
10
11/// Registers a synchronous application entry point for platform-controlled launch.
12///
13/// The annotated function must take no parameters and must not be `async`.
14/// Expands to an `extern "Rust"` `__cotis_launch_hook` symbol consumed by
15/// `cotis::launch::cotis_launch`.
16///
17/// # Examples
18///
19/// ```ignore
20/// use cotis_macros::cotis_start;
21///
22/// #[cotis_start]
23/// fn start() {
24///     // Application setup and UI loop.
25/// }
26/// ```
27#[proc_macro_attribute]
28pub fn cotis_start(_attr: TokenStream, item: TokenStream) -> TokenStream {
29    let input_fn = parse_macro_input!(item as ItemFn);
30
31    if !input_fn.sig.inputs.is_empty() {
32        return syn::Error::new_spanned(
33            &input_fn.sig,
34            "#[cotis_start] functions must not take parameters",
35        )
36        .to_compile_error()
37        .into();
38    }
39
40    if input_fn.sig.asyncness.is_some() {
41        return syn::Error::new_spanned(
42            &input_fn.sig,
43            "#[cotis_start] cannot be applied to async functions. Use #[cotis_start_async] instead.",
44        )
45        .to_compile_error()
46        .into();
47    }
48
49    let fn_name = &input_fn.sig.ident;
50
51    let expanded = quote! {
52        #input_fn
53
54        #[doc(hidden)]
55        #[unsafe(no_mangle)]
56        pub extern "Rust" fn __cotis_launch_hook() {
57            #fn_name();
58        }
59    };
60
61    expanded.into()
62}
63
64/// Registers an asynchronous application entry point for platform-controlled launch.
65///
66/// The annotated function must take no parameters. It may be `async` or return
67/// immediately; either way it is boxed into the future awaited by
68/// `cotis::launch::cotis_launch_async`.
69///
70/// # Examples
71///
72/// ```ignore
73/// use cotis_macros::cotis_start_async;
74///
75/// #[cotis_start_async]
76/// async fn start() {
77///     // Async application setup and UI loop.
78/// }
79/// ```
80#[proc_macro_attribute]
81pub fn cotis_start_async(_attr: TokenStream, item: TokenStream) -> TokenStream {
82    let input_fn = parse_macro_input!(item as ItemFn);
83
84    if !input_fn.sig.inputs.is_empty() {
85        return syn::Error::new_spanned(
86            &input_fn.sig,
87            "#[cotis_start_async] functions must not take parameters",
88        )
89        .to_compile_error()
90        .into();
91    }
92
93    let fn_name = &input_fn.sig.ident;
94    let hook_body = if input_fn.sig.asyncness.is_some() {
95        quote! {
96            Box::new(#fn_name())
97        }
98    } else {
99        quote! {
100            Box::new(async move {
101                #fn_name();
102            })
103        }
104    };
105
106    let expanded = quote! {
107        #input_fn
108
109        #[doc(hidden)]
110        #[unsafe(no_mangle)]
111        pub extern "Rust" fn __cotis_launch_async_hook() -> Box<dyn ::core::future::Future<Output = ()>> {
112            #hook_body
113        }
114    };
115
116    expanded.into()
117}