churust_macros/lib.rs
1//! Procedural macros for the [Churust](https://docs.rs/churust) web framework.
2//!
3//! This crate exposes a single attribute macro, [`main`], which turns an
4//! `async fn main()` into a synchronous entry point backed by a multi-threaded
5//! Tokio runtime. You should **not** depend on this crate directly; instead
6//! use the top-level `churust` crate and write `#[churust::main]`.
7//!
8//! # Quick start
9//!
10//! In your application's `main.rs`, after adding `churust` as a dependency:
11//!
12//! ```text
13//! #[churust::main]
14//! async fn main() -> std::io::Result<()> {
15//! // Churust::server()…start().await goes here.
16//! Ok(())
17//! }
18//! ```
19//!
20//! The examples in this crate are illustrations rather than doctests: the
21//! generated code names `::churust`, which cannot resolve from inside
22//! `churust-macros` because `churust` depends on this crate and not the
23//! reverse. Executable coverage lives in `churust/tests/macro_main.rs`.
24//!
25//! The attribute strips the `async` modifier and injects a Tokio runtime so
26//! that the Rust compiler sees an ordinary synchronous `fn main()`. This
27//! mirrors the ergonomics of `#[tokio::main]` while keeping the framework's
28//! entry-point convention explicit and framework-branded.
29//!
30//! # When to use this crate directly
31//!
32//! Almost never. The re-export via `churust::main` (or `churust::prelude::*`)
33//! is the intended consumption path. The only reason to take a direct
34//! dependency on `churust-macros` is if you are building a crate that wraps or
35//! extends Churust's attribute macros at a low level.
36
37#![deny(missing_docs)]
38
39use proc_macro::TokenStream;
40use quote::quote;
41use syn::{parse_macro_input, ItemFn};
42
43/// Turns `async fn main()` into a synchronous entry point that runs on a
44/// multi-threaded Tokio runtime.
45///
46/// Apply this attribute to your application's `main` function in place of
47/// `#[tokio::main]`. The attribute:
48///
49/// 1. Verifies that the annotated function is `async`. A compile error is
50/// emitted if it is not.
51/// 2. Removes the `async` keyword so the result is a valid synchronous `fn
52/// main` that the Rust runtime can call directly.
53/// 3. Wraps the original function body in a call to
54/// `tokio::runtime::Builder::new_multi_thread().enable_all().build()` and
55/// then blocks on the async body with `block_on`.
56///
57/// # Constraints
58///
59/// * The annotated function **must** be `async`. Applying the attribute to a
60/// non-async function is a hard compile error:
61///
62/// ```text
63/// error: #[churust::main] requires an `async fn`
64/// ```
65///
66/// * `tokio` must be a dependency (directly or transitively) of the crate that
67/// uses this attribute, because the expanded code references
68/// `::tokio::runtime::Builder`.
69///
70/// # Return type
71///
72/// The return type is preserved unchanged. Returning `std::io::Result<()>` is
73/// idiomatic because `Churust::server().start().await` returns that type:
74///
75/// ```text
76/// #[churust::main]
77/// async fn main() -> std::io::Result<()> {
78/// Ok(())
79/// }
80/// ```
81///
82/// Returning `()` is also valid when no top-level I/O error needs to be
83/// propagated:
84///
85/// ```text
86/// #[churust::main]
87/// async fn main() {
88/// // fire-and-forget setup, panics on failure
89/// }
90/// ```
91///
92/// # Attribute arguments
93///
94/// No arguments are accepted. The attribute token stream is intentionally
95/// ignored so that the macro does not trap future extensions, but passing any
96/// tokens currently has no effect.
97///
98/// # Generated code
99///
100/// Given the following input:
101///
102/// ```text
103/// #[churust::main]
104/// async fn main() -> std::io::Result<()> {
105/// do_something().await
106/// }
107/// ```
108///
109/// The macro expands to roughly the following synchronous function. The
110/// generated runtime variable uses a mangled name (`__rt`) to avoid shadowing
111/// anything in the caller's scope:
112///
113/// ```text
114/// fn main() -> std::io::Result<()> {
115/// let __rt = ::churust::__private::tokio::runtime::Builder::new_multi_thread()
116/// .enable_all()
117/// .build()
118/// .expect("failed to build tokio runtime");
119/// __rt.block_on(async move {
120/// do_something().await
121/// })
122/// }
123/// ```
124///
125/// The runtime is reached through `churust`'s re-export rather than `::tokio`,
126/// so an application needs only `churust` as a dependency.
127///
128/// All original attributes (e.g. `#[cfg(…)]`, doc comments) and visibility
129/// modifiers on the original function are forwarded to the generated function
130/// unchanged.
131#[proc_macro_attribute]
132pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
133 let input = parse_macro_input!(item as ItemFn);
134
135 if input.sig.asyncness.is_none() {
136 return syn::Error::new_spanned(
137 input.sig.fn_token,
138 "#[churust::main] requires an `async fn`",
139 )
140 .to_compile_error()
141 .into();
142 }
143
144 let attrs = &input.attrs;
145 let vis = &input.vis;
146 let sig = &input.sig;
147 let body = &input.block;
148 let output = &sig.output;
149 let ident = &sig.ident;
150
151 // Rebuild a non-async signature with the same name/return type.
152 let expanded = quote! {
153 #(#attrs)*
154 #vis fn #ident() #output {
155 let __rt = ::churust::__private::tokio::runtime::Builder::new_multi_thread()
156 .enable_all()
157 .build()
158 .expect("failed to build tokio runtime");
159 __rt.block_on(async move #body)
160 }
161 };
162
163 expanded.into()
164}