alux_sdk_macros/lib.rs
1#![doc = include_str!("../README.md")]
2
3mod trait_algebra;
4
5use crate::trait_algebra::trait_algebra_internal;
6use proc_macro::TokenStream;
7use syn::{ItemTrait, parse_macro_input};
8
9/// Reifies a trait as pure operation data and its fold.
10///
11/// In addition to re-emitting the annotated trait, the macro generates:
12///
13/// - `<Trait>Op`, with one inspectable variant per method and typed constructors;
14/// - `<Trait>Reply`, with one variant per returning method and typed `into_<method>` accessors;
15/// - `<Trait>Interpreter`, the mutable interpreter contract;
16/// - `<Trait>Op::interpret`, which folds one operation into an interpreter and returns its reply.
17///
18/// The generated operation enum has one `call` signature shared by every variant. That signature is
19/// synchronous only when every algebra method is synchronous. If any method is asynchronous, the
20/// shared `call` function is asynchronous for all variants, although synchronous handler branches
21/// still execute directly without awaiting.
22///
23/// Operations contain only method arguments. They do not contain an interpreter, return value, reply
24/// channel, Tokio type, or any other transport decision.
25///
26/// Attribute arguments are copied to both generated enums. For example,
27/// `#[trait_algebra(derive(Debug, Clone))]` derives `Debug` and `Clone` for both `<Trait>Op` and
28/// `<Trait>Reply`.
29///
30/// Associated types used by method arguments become generic parameters of `<Trait>Op`. Associated
31/// types used by return values become generic parameters of `<Trait>Reply`. The generated interpreter
32/// redeclares those associated types, and `interpret` binds the syntax carriers to the handler carriers.
33///
34/// # Example
35///
36/// ```
37/// # use alux_sdk_macros::trait_algebra;
38/// #[trait_algebra(derive(Debug, PartialEq))]
39/// trait Counter {
40/// async fn add(&self, amount: u64) -> u64;
41/// async fn reset(&self);
42/// }
43///
44/// #[derive(Default)]
45/// struct Total(u64);
46///
47/// impl CounterInterpreter for Total {
48/// async fn add(&mut self, amount: u64) -> u64 {
49/// self.0 += amount;
50/// self.0
51/// }
52///
53/// async fn reset(&mut self) {
54/// self.0 = 0;
55/// }
56/// }
57///
58/// # #[tokio::main(flavor = "current_thread")]
59/// # async fn main() {
60/// let operation = CounterOp::add(2);
61/// assert_eq!(operation, CounterOp::Add { amount: 2 });
62///
63/// let mut total = Total::default();
64/// let reply = operation.interpret(&mut total).await;
65/// assert_eq!(reply.into_add(), 2);
66///
67/// CounterOp::reset().interpret(&mut total).await;
68/// assert_eq!(total.0, 0);
69/// # }
70/// ```
71///
72/// # Transports
73///
74/// `transport` states the trait itself for whatever carries its operations to an interpreter
75/// elsewhere: a method stating a value asks and takes the value out of the reply, and a method
76/// stating none sends and does not stay. Both spellings emit the same bodies, and differ in who is
77/// allowed to name a type.
78///
79/// `transport = <Carrier>` names one carrier, resolved in the author's scope, and states
80/// `impl Trait for Carrier<TraitOp, TraitReply>`. It is the right form where the crate that owns the
81/// carrier states the impl.
82///
83/// Bare `transport`, or `transport = capability`, names none and states the impl for every witness
84/// of the capabilities:
85///
86/// ```ignore
87/// impl<Carrier> Counter for Carrier
88/// where
89/// Carrier: AlgebraCall<CounterOp, CounterReply> + AlgebraSend<CounterOp> + Send + Sync,
90/// ```
91///
92/// It is the right form where the trait's own crate states the impl, which the orphan rule makes
93/// every case where the trait is declared in a layer that must not know a transport. Only the
94/// capabilities the algebra needs are asked for: `AlgebraCall` where any method states a value, and
95/// `AlgebraSend` where any states none. `Send + Sync` is asked for because the impl reaches the
96/// carrier through a reference, which is also what lets the trait state that its calls may be
97/// awaited in another task, as `#[trait_variant::make(Send)]` does.
98///
99/// Since both capabilities forward through `&` and `Arc`, a borrow and a share of a carrier are the
100/// algebra too, so the trait needs no `auto_impl` of its own, and carrying one would conflict with
101/// the blanket impl.
102///
103/// Either way, transport is stated only for an algebra whose carriers are all chosen and whose
104/// methods are all asynchronous. `alux-tokio` carries the generated operation and reply types over
105/// a bounded channel while leaving stream consumption to the application.
106#[proc_macro_attribute]
107pub fn trait_algebra(attribute: TokenStream, item: TokenStream) -> TokenStream {
108 let definition = parse_macro_input!(item as ItemTrait);
109
110 trait_algebra_internal(attribute.into(), &definition).into()
111}