Skip to main content

guinea_macros/
lib.rs

1use proc_macro::TokenStream;
2use syn::{ItemFn, parse_macro_input};
3
4mod actor_dsl;
5mod app_dsl;
6mod elm;
7mod feature_dsl;
8mod handler;
9mod harness_test;
10mod installs;
11mod mark;
12mod reducer;
13mod remote;
14mod request;
15mod routes_dsl;
16mod segment;
17
18/// A feature's manifest: its name, and the reducers it exports.
19///
20/// ```ignore
21/// feature! {
22///     pub Tabs {
23///         exports { contracts::Tabs }
24///     }
25/// }
26/// ```
27///
28/// It makes the feature's type - one `Bound` per export, in the order listed -
29/// and what it exports. What installs it is an [`installs`] function.
30#[proc_macro]
31pub fn feature(input: TokenStream) -> TokenStream {
32    feature_dsl::feature_impl(input)
33}
34
35/// The application's manifest: its name, and the features and plugins whose
36/// exports its pages read.
37///
38/// ```ignore
39/// app! {
40///     pub App {
41///         installs { ActivityFeature, L10nPlugin<Strings>, #[cfg(debug_assertions)] Overlay }
42///     }
43/// }
44/// ```
45///
46/// It makes the application's type - one `Installed` per line, in the order
47/// listed - and the top segment a route tree names with `app = App`. What
48/// installs it is an [`installs`] function taking `&mut FeatureBuilder`.
49#[proc_macro]
50pub fn app(input: TokenStream) -> TokenStream {
51    app_dsl::app_impl(input)
52}
53
54/// The function that installs a feature: whatever it returns is the feature,
55/// its second argument is what it is installed with.
56///
57/// ```ignore
58/// #[installs]
59/// fn tabs(cx: &FeatureInitContext, context: &str) -> anyhow::Result<Tabs> {
60///     let (tabs, _) = cx.state::<contracts::Tabs>().driven_by(|push| TabsActor::new(push));
61///     Ok(Tabs(tabs))
62/// }
63/// ```
64///
65/// A function taking `&mut FeatureBuilder` installs the application `app!`
66/// declared instead:
67///
68/// ```ignore
69/// #[installs]
70/// fn app(app: &mut FeatureBuilder) -> anyhow::Result<App> {
71///     app.plugin(DevToolsPlugin::new())?;
72///     Ok(App(app.feature(ActivityFeature)?, app.plugin(L10nPlugin::new("en"))?))
73/// }
74/// ```
75///
76/// Named for what the function does rather than for the trait: `#[feature]`
77/// is ambiguous with Rust's own `feature` attribute.
78#[proc_macro_attribute]
79pub fn installs(_attr: TokenStream, item: TokenStream) -> TokenStream {
80    installs::installs_impl(item)
81}
82
83/// Makes a function a reducer: `impl Reducer` written from its signature. The
84/// state is what the first argument borrows mutably, the update is the second
85/// argument's type:
86///
87/// <!-- shown: a reducer -->
88/// ```rust,ignore
89/// #[derive(Default, Clone, PartialEq, Debug)]
90/// pub struct Count(pub u32);
91///
92/// #[reducer]
93/// fn count(this: &mut Count, by: u32) {
94///     this.0 += by;
95/// }
96/// ```
97/// <!-- /shown -->
98///
99/// An update with more than one shape is an enum, and the function matches on
100/// it - destructured right in the argument when there is one shape only:
101///
102/// <!-- shown: a reducer of an enum -->
103/// ```rust,ignore
104/// #[derive(Default, Clone, PartialEq, Debug)]
105/// pub struct Table {
106///     pub rows: Vec<String>,
107///     pub descending: bool,
108/// }
109///
110/// #[derive(Clone, Debug)]
111/// pub enum Changed {
112///     Rows(Vec<String>),
113///     Sorted { descending: bool },
114/// }
115///
116/// #[reducer]
117/// fn table(this: &mut Table, changed: Changed) {
118///     match changed {
119///         Changed::Rows(rows) => this.rows = rows,
120///         Changed::Sorted { descending } => this.descending = descending,
121///     }
122/// }
123/// ```
124/// <!-- /shown -->
125///
126/// Two arguments, no more. A reducer knows its state and what changed it, not
127/// who asked, so there is no context to take; it is not `async`, and returns
128/// nothing. `impl Reducer` written by hand is the same thing.
129#[proc_macro_attribute]
130pub fn reducer(_attr: TokenStream, item: TokenStream) -> TokenStream {
131    let input = parse_macro_input!(item as ItemFn);
132    reducer::reducer_impl(input).into()
133}
134
135/// A test run once per seed, each time on a fresh `Harness`, with the order of
136/// everything it sets off decided by the seed.
137///
138/// A failing seed is named in the panic; `SEED=<n> cargo test` runs that order
139/// alone, and again.
140///
141/// `exclusive = "key"` runs it one at a time with every other test in the
142/// process that names the same key - for what a process has one of, a global
143/// store say, which tests on their own threads would otherwise fight over.
144///
145/// ```ignore
146/// #[guinea::test(iterations = 200)]
147/// fn the_latest_query_wins(h: &mut Harness) {
148///     h.install::<Search>(&()).unwrap();
149///     h.dispatch::<Results>().emit(Query("gu".into()));
150///     h.dispatch::<Results>().emit(Query("guinea".into()));
151///     h.settled();
152///     assert_eq!(h.state::<Results>().query, "guinea");
153/// }
154/// ```
155#[proc_macro_attribute]
156pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
157    harness_test::test_impl(attr, item)
158}
159
160/// Writes `type Installs = ();` and the `install` that goes with it, for a
161/// page or layout that installs nothing.
162///
163/// Only that. Declaring what a segment installs is what makes the declaration
164/// an obligation of the body; declaring that it installs *nothing* is
165/// ceremony, and stable Rust has no conditional default body to remove it.
166///
167/// ```ignore
168/// #[segment]
169/// impl Page for Splash {
170///     type Params = ();
171///     fn view(cx: &mut PageCx<'_>) { .. }
172/// }
173/// ```
174#[proc_macro_attribute]
175pub fn segment(_attr: TokenStream, item: TokenStream) -> TokenStream {
176    segment::segment_impl(item)
177}
178
179/// Declares a slot: a place in a layout's view that a segment below fills.
180///
181/// ```ignore
182/// #[slot]
183/// pub struct Toolbar;
184/// ```
185#[proc_macro_attribute]
186pub fn slot(_attr: TokenStream, item: TokenStream) -> TokenStream {
187    routes_dsl::slot_impl(item)
188}
189
190/// Writes down what an `impl Page` for the iced backend left out.
191///
192/// ```ignore
193/// #[page]
194/// impl Page for Services {
195///     type Params = ServicesParams;
196///
197///     fn install(ctx: &FeatureInitContext, _: &Self::Params) -> anyhow::Result<()> { .. }
198///     fn view(&self, cx: &PageCx<'_>) -> View<Self::Message> { .. }
199/// }
200/// ```
201///
202/// An omitted `Params` becomes `()` and an omitted `Message` becomes
203/// `Infallible`; a node with the defaulted message also gets the empty
204/// `update` that goes with it. Nothing else - a macro that derived a
205/// declaration from a body would be a second source of truth wearing the
206/// clothes of one.
207#[proc_macro_attribute]
208pub fn iced_page(_attr: TokenStream, item: TokenStream) -> TokenStream {
209    elm::node_impl(item, elm::Kind::Page, "guinea-iced", "iced")
210}
211
212/// [`iced_page`] for a layout, which has no route parameters of its own.
213#[proc_macro_attribute]
214pub fn iced_layout(_attr: TokenStream, item: TokenStream) -> TokenStream {
215    elm::node_impl(item, elm::Kind::Layout, "guinea-iced", "iced")
216}
217
218/// [`iced_page`] for the windows-reactor backend.
219///
220/// The same macro because the two backends are the same kind of thing: the
221/// reactor's second preview is Elm - state in structs, events as enums - so a
222/// page there has the same five items a page here does, and leaving out the
223/// empty ones is the same job.
224#[proc_macro_attribute]
225pub fn winui_page(_attr: TokenStream, item: TokenStream) -> TokenStream {
226    elm::node_impl(item, elm::Kind::Page, "guinea-winui", "winui")
227}
228
229/// [`winui_page`] for a layout.
230#[proc_macro_attribute]
231pub fn winui_layout(_attr: TokenStream, item: TokenStream) -> TokenStream {
232    elm::node_impl(item, elm::Kind::Layout, "guinea-winui", "winui")
233}
234
235/// Declares an actor's manifest:
236///
237/// ```ignore
238/// actor! {
239///     ProcessActor<P: ProcessesPort + 'static> {
240///         handlers   { Kill, Refresh }
241///         publishes  { ProcessKilled }
242///         subscribes { SettingsChanged }
243///     }
244/// }
245/// ```
246#[proc_macro]
247pub fn actor(input: TokenStream) -> TokenStream {
248    actor_dsl::actor_impl(input)
249}
250
251/// `routes! { Route { layout(TabsLayout) { page(Processes) link("/:context/processes")
252/// { context: String } ... } } }` - the tree's `{}` nesting *is* the segment
253/// chain (no attribute stack to track); `page(...)`'s type also names the
254/// generated variant, so there's one name per leaf, not two kept in sync by
255/// hand. Generates the enum itself, `link` and `deep_links` for the routes
256/// that agreed to have an address, and `RouteChain` (enum -> segment chain).
257///
258/// `app = App,` before the tree, beside `backend`, names the application
259/// `app!` declared: it is the top segment of every chain, so its pages read
260/// what it installs. Navigating panics when the application running is not
261/// that one.
262#[proc_macro]
263pub fn routes(input: TokenStream) -> TokenStream {
264    routes_dsl::routes_impl(input)
265}
266
267/// Puts a type on the global bus: `impl Event for T {}`.
268#[proc_macro_derive(Event)]
269pub fn event(item: TokenStream) -> TokenStream {
270    let input = parse_macro_input!(item as syn::DeriveInput);
271    let gc = handler::guinea_core_crate_path();
272
273    let name = &input.ident;
274    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
275
276    quote::quote! {
277        impl #impl_generics #gc::actor::event_bus::Event for #name #ty_generics #where_clause {}
278    }
279    .into()
280}
281
282/// Makes a type a request on the global bus, answered with `reply`: the
283/// request names its answer where the request is declared.
284///
285/// <!-- shown: a request and its reply -->
286/// ```rust,ignore
287/// #[derive(Clone, Debug, guinea::Request)]
288/// #[request(reply = Outcome)]
289/// pub struct Kill(pub u32);
290///
291/// #[derive(Clone, Debug, PartialEq)]
292/// pub enum Outcome {
293///     Done,
294///     Denied,
295/// }
296/// ```
297/// <!-- /shown -->
298///
299/// Exactly one subscriber answers it: a handler that returns the reply. The
300/// generated code sends what it returns back to whoever asked, once - and the
301/// `async` form does the same after its body resolves. The feature that owns
302/// the actor subscribes it to the request on the global bus, so it answers
303/// while the segment that installed it stands:
304///
305/// <!-- shown: the one that answers -->
306/// ```rust,ignore
307/// #[derive(Debug, Default)]
308/// pub struct Processes {
309///     protected: Vec<u32>,
310/// }
311///
312/// actor! {
313///     Processes {
314///         handlers { RpcRequest<Kill> }
315///     }
316/// }
317///
318/// // Returning the reply is what makes it the answer: the generated code
319/// // sends it back, once.
320/// #[handler]
321/// fn kill(this: &mut Processes, Kill(pid): Kill) -> Outcome {
322///     match this.protected.contains(&pid) {
323///         true => Outcome::Denied,
324///         false => Outcome::Done,
325///     }
326/// }
327///
328/// feature! {
329///     pub Killing {}
330/// }
331///
332/// // It answers for as long as the segment that installed it stands.
333/// #[installs]
334/// fn killing(cx: &FeatureInitContext) -> anyhow::Result<Killing> {
335///     let processes = cx.spawn(Processes { protected: vec![4] });
336///     processes.subscribe_on::<RpcRequest<Kill>>(Bus::Global);
337///     Ok(Killing)
338/// }
339/// ```
340/// <!-- /shown -->
341///
342/// Asking is `AsyncBus::request`, awaited off the UI thread. It resolves to
343/// the reply, or to an error saying why there is none: nothing answers the
344/// request, the one that does sleeps in a `keep` segment, or the timeout ran
345/// out. The first two are known before anything is published, so they come
346/// back at once:
347///
348/// <!-- shown: asking -->
349/// ```rust,ignore
350/// pub struct Ask(pub u32);
351/// pub struct Told(pub String);
352///
353/// #[derive(Debug)]
354/// pub struct Asker {
355///     push: Push<Said>,
356/// }
357///
358/// actor! {
359///     Asker {
360///         handlers { Ask, Told }
361///     }
362/// }
363///
364/// // Asked off the UI thread, and answered with what the one answerer
365/// // returned - or with why there was nothing to wait for.
366/// #[handler]
367/// async fn ask(cx: AsyncContext<Asker>, Ask(pid): Ask) {
368///     let said = match AsyncBus::request(Kill(pid), Duration::from_secs(5)).await {
369///         Ok(outcome) => format!("{outcome:?}"),
370///         Err(error) => error.to_string(),
371///     };
372///     cx.send(Told(said));
373/// }
374///
375/// #[handler]
376/// fn told(this: &mut Asker, Told(said): Told) {
377///     this.push.send(said);
378/// }
379/// ```
380/// <!-- /shown -->
381///
382/// Anything else subscribed to the request only hears it. A handler that
383/// returns nothing is told what was asked and cannot answer - its reply goes
384/// nowhere:
385///
386/// <!-- shown: one that only hears it -->
387/// ```rust,ignore
388/// #[derive(Debug, Default)]
389/// pub struct Audit {
390///     pub seen: Vec<u32>,
391/// }
392///
393/// actor! {
394///     Audit {
395///         handlers { RpcRequest<Kill> }
396///     }
397/// }
398///
399/// // Returns nothing, so it hears the request and cannot answer it: there
400/// // is one answerer, and it is not this.
401/// #[handler]
402/// fn heard(this: &mut Audit, request: RpcRequest<Kill>) {
403///     this.seen.push(request.payload.0);
404/// }
405///
406/// feature! {
407///     pub Auditing {}
408/// }
409///
410/// #[installs]
411/// fn auditing(cx: &FeatureInitContext) -> anyhow::Result<Auditing> {
412///     let audit = cx.spawn(Audit::default());
413///     audit.subscribe_on::<RpcRequest<Kill>>(Bus::Global);
414///     Ok(Auditing)
415/// }
416/// ```
417/// <!-- /shown -->
418///
419/// A second answerer on the same bus is a setup bug, and is refused - with a
420/// panic naming both - when it subscribes, rather than racing the first one
421/// for every request. A closure answers with `GlobalEventBus::answer_fn`.
422#[proc_macro_derive(Request, attributes(request))]
423pub fn request(item: TokenStream) -> TokenStream {
424    let input = parse_macro_input!(item as syn::DeriveInput);
425    request::derive_request(input).into()
426}
427
428/// Makes an enum of unit variants the application's marks: each variant is a
429/// name, written as the variant is.
430#[proc_macro_derive(Mark)]
431pub fn mark(item: TokenStream) -> TokenStream {
432    let input = parse_macro_input!(item as syn::DeriveInput);
433    mark::derive_mark(input).into()
434}
435
436/// Lets a tool send this type to the running application as JSON: as an
437/// action to whichever scope answers it, as an event on the global bus, or
438/// both. The type derives `serde::Deserialize` too.
439///
440/// ```ignore
441/// #[derive(Clone, Debug, Deserialize, guinea::Remote)]
442/// #[remote(action)]
443/// pub struct Kill(pub u32);
444/// ```
445#[proc_macro_derive(Remote, attributes(remote))]
446pub fn remote(item: TokenStream) -> TokenStream {
447    let input = parse_macro_input!(item as syn::DeriveInput);
448    remote::derive_remote(input).into()
449}
450
451/// Makes a function an actor's handler for one message: `impl Handler<M>`
452/// written from its signature.
453///
454/// A handler takes what it uses and nothing else. The actor, then the message
455/// itself - destructured right in the argument when that reads better:
456///
457/// <!-- shown: a handler that takes the message -->
458/// ```rust,ignore
459/// pub struct Add(pub u32);
460///
461/// #[derive(Debug)]
462/// pub struct Counting {
463///     push: Push<Count>,
464/// }
465///
466/// actor! {
467///     Counting {
468///         handlers { Add }
469///     }
470/// }
471///
472/// #[handler]
473/// fn add(this: &mut Counting, Add(by): Add) {
474///     this.push.send(by);
475/// }
476/// ```
477/// <!-- /shown -->
478///
479/// A handler that sends on, publishes, or starts background work takes a
480/// third argument, its `Cx`. Written bare: the actor and the message are the
481/// two arguments before it, and the macro writes them in. `Cx` is typed by the
482/// message it handles, which is what `actor!`'s flow checks - a handler of
483/// `Refresh` declared `Refresh => { bg Counted }` may spawn work that answers
484/// `Counted`, and nothing else compiles:
485///
486/// <!-- shown: a handler that starts work -->
487/// ```rust,ignore
488/// pub struct Refresh;
489/// pub struct Counted(pub u32);
490///
491/// #[derive(Debug)]
492/// pub struct Counting {
493///     push: Push<Count>,
494/// }
495///
496/// actor! {
497///     Counting {
498///         handlers { Refresh => { bg Counted }, Counted }
499///     }
500/// }
501///
502/// // `cx` is `Cx<Counting, Refresh>`: the macro writes in what the two
503/// // arguments before it already say.
504/// #[handler]
505/// fn refresh(_this: &mut Counting, _: Refresh, cx: Cx) {
506///     cx.spawn_bg::<Counted, _>(async {
507///         guinea::core::executor::random_delay().await;
508///         Counted(7)
509///     });
510/// }
511///
512/// #[handler]
513/// fn counted(this: &mut Counting, Counted(n): Counted) {
514///     this.push.send(n);
515/// }
516/// ```
517/// <!-- /shown -->
518///
519/// An `async fn` runs off the UI thread. It takes the actor's
520/// `AsyncContext` instead of the actor - the actor itself stays on the UI
521/// thread - and ends with the actor: dropped at its next await once the actor
522/// is gone, unless it listens for that itself. Work that needs the actor's
523/// state is a plain handler that reads it and hands the rest to `spawn_bg`,
524/// as above: the state is read when the message comes, on the UI thread.
525///
526/// <!-- shown: an async handler -->
527/// ```rust,ignore
528/// pub struct Load(pub u32);
529/// pub struct Loaded(pub u32);
530///
531/// #[derive(Debug)]
532/// pub struct Loader {
533///     push: Push<Count>,
534/// }
535///
536/// actor! {
537///     Loader {
538///         handlers { Load, Loaded }
539///     }
540/// }
541///
542/// // Runs off the UI thread, and ends with the actor: `cx` knows when it is
543/// // gone, and sends back to it while it is not.
544/// #[handler]
545/// async fn load(cx: AsyncContext<Loader>, Load(n): Load) {
546///     guinea::core::executor::random_delay().await;
547///     cx.send(Loaded(n * 2));
548/// }
549///
550/// #[handler]
551/// fn loaded(this: &mut Loader, Loaded(n): Loaded) {
552///     this.push.send(n);
553/// }
554/// ```
555/// <!-- /shown -->
556///
557/// A return type makes the handler an answer to `AsyncBus::request`: the
558/// value it returns is the reply, sent exactly once, by the generated code and
559/// nothing else. That holds for both the plain and the `async` form. A plain
560/// handler whose answer needs background work returns `Reply<T>`: it reads
561/// what it needs from the actor and answers with `Reply::later(..)`, or with
562/// `Reply::now(..)` when there is nothing to wait for.
563#[proc_macro_attribute]
564pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream {
565    let input = parse_macro_input!(item as ItemFn);
566    handler::generate_standalone_handler(input)
567}