guinea_macros/lib.rs
1use proc_macro::TokenStream;
2use syn::{ItemFn, parse_macro_input};
3
4mod actor_dsl;
5mod elm;
6mod feature_dsl;
7mod handler;
8mod harness_test;
9mod installs;
10mod mark;
11mod reducer;
12mod remote;
13mod routes_dsl;
14mod segment;
15
16/// A feature's manifest: its name, and the reducers it exports.
17///
18/// ```ignore
19/// feature! {
20/// pub Tabs {
21/// exports { contracts::Tabs }
22/// }
23/// }
24/// ```
25///
26/// It makes the feature's type - one `Bound` per export, in the order listed -
27/// and what it exports. What installs it is an [`installs`] function.
28#[proc_macro]
29pub fn feature(input: TokenStream) -> TokenStream {
30 feature_dsl::feature_impl(input)
31}
32
33/// The function that installs a feature: whatever it returns is the feature,
34/// its second argument is what it is installed with.
35///
36/// ```ignore
37/// #[installs]
38/// fn tabs(cx: &FeatureInitContext, context: &str) -> anyhow::Result<Tabs> {
39/// let (tabs, _) = cx.state::<contracts::Tabs>().driven_by(|push| TabsActor::new(push));
40/// Ok(Tabs(tabs))
41/// }
42/// ```
43///
44/// Named for what the function does rather than for the trait: `#[feature]`
45/// is ambiguous with Rust's own `feature` attribute.
46#[proc_macro_attribute]
47pub fn installs(_attr: TokenStream, item: TokenStream) -> TokenStream {
48 installs::installs_impl(item)
49}
50
51/// Makes a function a reducer: `impl Reducer` written from its signature. The
52/// state is what the first argument borrows mutably, the update is the second
53/// argument's type:
54///
55/// <!-- shown: a reducer -->
56/// ```rust,ignore
57/// #[derive(Default, Clone, PartialEq, Debug)]
58/// pub struct Count(pub u32);
59///
60/// #[reducer]
61/// fn count(this: &mut Count, by: u32) {
62/// this.0 += by;
63/// }
64/// ```
65/// <!-- /shown -->
66///
67/// An update with more than one shape is an enum, and the function matches on
68/// it - destructured right in the argument when there is one shape only:
69///
70/// <!-- shown: a reducer of an enum -->
71/// ```rust,ignore
72/// #[derive(Default, Clone, PartialEq, Debug)]
73/// pub struct Table {
74/// pub rows: Vec<String>,
75/// pub descending: bool,
76/// }
77///
78/// #[derive(Clone, Debug)]
79/// pub enum Changed {
80/// Rows(Vec<String>),
81/// Sorted { descending: bool },
82/// }
83///
84/// #[reducer]
85/// fn table(this: &mut Table, changed: Changed) {
86/// match changed {
87/// Changed::Rows(rows) => this.rows = rows,
88/// Changed::Sorted { descending } => this.descending = descending,
89/// }
90/// }
91/// ```
92/// <!-- /shown -->
93///
94/// Two arguments, no more. A reducer knows its state and what changed it, not
95/// who asked, so there is no context to take; it is not `async`, and returns
96/// nothing. `impl Reducer` written by hand is the same thing.
97#[proc_macro_attribute]
98pub fn reducer(_attr: TokenStream, item: TokenStream) -> TokenStream {
99 let input = parse_macro_input!(item as ItemFn);
100 reducer::reducer_impl(input).into()
101}
102
103/// A test run once per seed, each time on a fresh `Harness`, with the order of
104/// everything it sets off decided by the seed.
105///
106/// A failing seed is named in the panic; `SEED=<n> cargo test` runs that order
107/// alone, and again.
108///
109/// `exclusive = "key"` runs it one at a time with every other test in the
110/// process that names the same key - for what a process has one of, a global
111/// store say, which tests on their own threads would otherwise fight over.
112///
113/// ```ignore
114/// #[guinea::test(iterations = 200)]
115/// fn the_latest_query_wins(h: &mut Harness) {
116/// h.install::<Search>(&()).unwrap();
117/// h.dispatch::<Results>().emit(Query("gu".into()));
118/// h.dispatch::<Results>().emit(Query("guinea".into()));
119/// h.settled();
120/// assert_eq!(h.state::<Results>().query, "guinea");
121/// }
122/// ```
123#[proc_macro_attribute]
124pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
125 harness_test::test_impl(attr, item)
126}
127
128/// Writes `type Installs = ();` and the `install` that goes with it, for a
129/// page or layout that installs nothing.
130///
131/// Only that. Declaring what a segment installs is what makes the declaration
132/// an obligation of the body; declaring that it installs *nothing* is
133/// ceremony, and stable Rust has no conditional default body to remove it.
134///
135/// ```ignore
136/// #[segment]
137/// impl Page for Splash {
138/// type Params = ();
139/// fn view(cx: &mut PageCx<'_>) { .. }
140/// }
141/// ```
142#[proc_macro_attribute]
143pub fn segment(_attr: TokenStream, item: TokenStream) -> TokenStream {
144 segment::segment_impl(item)
145}
146
147/// Writes down what an `impl Page` for the iced backend left out.
148///
149/// ```ignore
150/// #[page]
151/// impl Page for Services {
152/// type Params = ServicesParams;
153///
154/// fn install(ctx: &FeatureInitContext, _: &Self::Params) -> anyhow::Result<()> { .. }
155/// fn view(&self, cx: &PageCx<'_>) -> View<Self::Message> { .. }
156/// }
157/// ```
158///
159/// An omitted `Params` becomes `()` and an omitted `Message` becomes
160/// `Infallible`; a node with the defaulted message also gets the empty
161/// `update` that goes with it. Nothing else - a macro that derived a
162/// declaration from a body would be a second source of truth wearing the
163/// clothes of one.
164#[proc_macro_attribute]
165pub fn iced_page(_attr: TokenStream, item: TokenStream) -> TokenStream {
166 elm::node_impl(item, elm::Kind::Page, "guinea-iced", "iced")
167}
168
169/// [`iced_page`] for a layout, which has no route parameters of its own.
170#[proc_macro_attribute]
171pub fn iced_layout(_attr: TokenStream, item: TokenStream) -> TokenStream {
172 elm::node_impl(item, elm::Kind::Layout, "guinea-iced", "iced")
173}
174
175/// [`iced_page`] for the windows-reactor backend.
176///
177/// The same macro because the two backends are the same kind of thing: the
178/// reactor's second preview is Elm - state in structs, events as enums - so a
179/// page there has the same five items a page here does, and leaving out the
180/// empty ones is the same job.
181#[proc_macro_attribute]
182pub fn winui_page(_attr: TokenStream, item: TokenStream) -> TokenStream {
183 elm::node_impl(item, elm::Kind::Page, "guinea-winui", "winui")
184}
185
186/// [`winui_page`] for a layout.
187#[proc_macro_attribute]
188pub fn winui_layout(_attr: TokenStream, item: TokenStream) -> TokenStream {
189 elm::node_impl(item, elm::Kind::Layout, "guinea-winui", "winui")
190}
191
192/// Declares an actor's manifest:
193///
194/// ```ignore
195/// actor! {
196/// ProcessActor<P: ProcessesPort + 'static> {
197/// handlers { Kill, Refresh }
198/// publishes { ProcessKilled }
199/// subscribes { SettingsChanged }
200/// }
201/// }
202/// ```
203#[proc_macro]
204pub fn actor(input: TokenStream) -> TokenStream {
205 actor_dsl::actor_impl(input)
206}
207
208/// `routes! { Route { layout(TabsLayout) { page(Processes) link("/:context/processes")
209/// { context: String } ... } } }` - the tree's `{}` nesting *is* the segment
210/// chain (no attribute stack to track); `page(...)`'s type also names the
211/// generated variant, so there's one name per leaf, not two kept in sync by
212/// hand. Generates the enum itself plus `path`/`parse` (string <-> enum),
213/// `RouteChain` (enum -> segment chain), and `ToUri` (enum -> `AppUri`, just
214/// the generated `.path()` string parsed - no per-app glue needed).
215#[proc_macro]
216pub fn routes(input: TokenStream) -> TokenStream {
217 routes_dsl::routes_impl(input)
218}
219
220/// Puts a type on the global bus: `impl Event for T {}`.
221#[proc_macro_derive(Event)]
222pub fn event(item: TokenStream) -> TokenStream {
223 let input = parse_macro_input!(item as syn::DeriveInput);
224 let gc = handler::guinea_core_crate_path();
225
226 let name = &input.ident;
227 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
228
229 quote::quote! {
230 impl #impl_generics #gc::actor::event_bus::Event for #name #ty_generics #where_clause {}
231 }
232 .into()
233}
234
235/// Makes an enum of unit variants the application's marks: each variant is a
236/// name, written as the variant is.
237#[proc_macro_derive(Mark)]
238pub fn mark(item: TokenStream) -> TokenStream {
239 let input = parse_macro_input!(item as syn::DeriveInput);
240 mark::derive_mark(input).into()
241}
242
243/// Lets a tool send this type to the running application as JSON: as an
244/// action to whichever scope answers it, as an event on the global bus, or
245/// both. The type derives `serde::Deserialize` too.
246///
247/// ```ignore
248/// #[derive(Clone, Debug, Deserialize, guinea::Remote)]
249/// #[remote(action)]
250/// pub struct Kill(pub u32);
251/// ```
252#[proc_macro_derive(Remote, attributes(remote))]
253pub fn remote(item: TokenStream) -> TokenStream {
254 let input = parse_macro_input!(item as syn::DeriveInput);
255 remote::derive_remote(input).into()
256}
257
258/// Makes a function an actor's handler for one message: `impl Handler<M>`
259/// written from its signature.
260///
261/// A handler takes what it uses and nothing else. The actor, then the message
262/// itself - destructured right in the argument when that reads better:
263///
264/// <!-- shown: a handler that takes the message -->
265/// ```rust,ignore
266/// pub struct Add(pub u32);
267///
268/// #[derive(Debug)]
269/// pub struct Counting {
270/// push: Push<Count>,
271/// }
272///
273/// actor! {
274/// Counting {
275/// handlers { Add }
276/// }
277/// }
278///
279/// #[handler]
280/// fn add(this: &mut Counting, Add(by): Add) {
281/// this.push.send(by);
282/// }
283/// ```
284/// <!-- /shown -->
285///
286/// A handler that sends on, publishes, or starts background work takes a
287/// third argument, its `Cx`. Written bare: the actor and the message are the
288/// two arguments before it, and the macro writes them in. `Cx` is typed by the
289/// message it handles, which is what `actor!`'s flow checks - a handler of
290/// `Refresh` declared `Refresh => { bg Counted }` may spawn work that answers
291/// `Counted`, and nothing else compiles:
292///
293/// <!-- shown: a handler that starts work -->
294/// ```rust,ignore
295/// pub struct Refresh;
296/// pub struct Counted(pub u32);
297///
298/// #[derive(Debug)]
299/// pub struct Counting {
300/// push: Push<Count>,
301/// }
302///
303/// actor! {
304/// Counting {
305/// handlers { Refresh => { bg Counted }, Counted }
306/// }
307/// }
308///
309/// // `cx` is `Cx<Counting, Refresh>`: the macro writes in what the two
310/// // arguments before it already say.
311/// #[handler]
312/// fn refresh(_this: &mut Counting, _: Refresh, cx: Cx) {
313/// cx.spawn_bg::<Counted, _>(async {
314/// guinea::core::executor::random_delay().await;
315/// Counted(7)
316/// });
317/// }
318///
319/// #[handler]
320/// fn counted(this: &mut Counting, Counted(n): Counted) {
321/// this.push.send(n);
322/// }
323/// ```
324/// <!-- /shown -->
325///
326/// An `async fn` runs off the UI thread. It takes the actor's
327/// `AsyncContext` instead of the actor - the actor itself stays on the UI
328/// thread - and ends with the actor: dropped at its next await once the actor
329/// is gone, unless it listens for that itself.
330///
331/// <!-- shown: an async handler -->
332/// ```rust,ignore
333/// pub struct Load(pub u32);
334/// pub struct Loaded(pub u32);
335///
336/// #[derive(Debug)]
337/// pub struct Loader {
338/// push: Push<Count>,
339/// }
340///
341/// actor! {
342/// Loader {
343/// handlers { Load, Loaded }
344/// }
345/// }
346///
347/// // Runs off the UI thread, and ends with the actor: `cx` knows when it is
348/// // gone, and sends back to it while it is not.
349/// #[handler]
350/// async fn load(cx: AsyncContext<Loader>, Load(n): Load) {
351/// guinea::core::executor::random_delay().await;
352/// cx.send(Loaded(n * 2));
353/// }
354///
355/// #[handler]
356/// fn loaded(this: &mut Loader, Loaded(n): Loaded) {
357/// this.push.send(n);
358/// }
359/// ```
360/// <!-- /shown -->
361///
362/// A return type makes the handler an answer to `AsyncBus::request`: the
363/// value it returns is the reply, sent exactly once, by the generated code and
364/// nothing else. That holds for both the plain and the `async` form.
365#[proc_macro_attribute]
366pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
367 let input = parse_macro_input!(item as ItemFn);
368 handler::generate_standalone_handler(input)
369}