Skip to main content

recallable_macro/
lib.rs

1//! # Recallable Macro
2//!
3//! Procedural macros backing the `recallable` crate.
4//!
5//! Provided macros:
6//!
7//! - `#[recallable_model]`: injects `Recallable`/`Recall` derives; with the `serde`
8//!   Cargo feature enabled for this macro crate it also adds `serde::Serialize`
9//!   and applies `#[serde(skip)]` to fields marked `#[recallable(skip)]`.
10//!
11//! - `#[derive(Recallable)]`: generates an internal companion memento struct, exposes
12//!   it as `<Struct as Recallable>::Memento`, and emits the `Recallable` impl; with the
13//!   `impl_from` Cargo feature it also generates `From<Struct>` for the memento type.
14//!
15//! - `#[derive(Recall)]`: generates the `Recall` implementation and recursively
16//!   recalls fields annotated with `#[recallable]`.
17//!
18//! Feature flags are evaluated in the `recallable-macro` crate itself. See `context`
19//! for details about the generated memento struct and trait implementations.
20
21use proc_macro::TokenStream;
22
23use quote::quote;
24use syn::{DeriveInput, parse_macro_input};
25
26mod context;
27mod model_macro;
28
29/// Attribute macro that augments a struct with `Recallable`/`Recall` derives.
30///
31/// - Always adds `#[derive(Recallable, Recall)]`.
32/// - When the `serde` feature is enabled for the macro crate, it also adds
33///   `#[derive(serde::Serialize)]`.
34/// - For fields annotated with `#[recallable(skip)]`, it injects `#[serde(skip)]`
35///   to keep serde output aligned with recall behavior.
36/// - This attribute itself takes no arguments.
37///
38/// This macro preserves the original struct shape and only mutates attributes.
39///
40/// **Attribute ordering:** This macro must appear *before* any attributes it needs
41/// to inspect. An attribute macro only receives attributes that follow it in source
42/// order. For example, `#[derive(Serialize)]` placed above `#[recallable_model]` is
43/// invisible to the macro and will cause a duplicate-derive error.
44#[proc_macro_attribute]
45pub fn recallable_model(attr: TokenStream, item: TokenStream) -> TokenStream {
46    model_macro::expand(attr, item)
47}
48
49/// Derive macro that generates the companion memento type and `Recallable` impl.
50///
51/// The generated memento type:
52/// - mirrors the original struct shape (named/tuple/unit),
53/// - includes fields unless marked with `#[recallable(skip)]`,
54/// - uses the same visibility as the input struct,
55/// - keeps all generated fields private by omitting field-level visibility modifiers,
56/// - also derives `serde::Deserialize` when the `serde` feature is enabled for the
57///   macro crate.
58///
59/// For `#[recallable]` fields, the generated memento field type is exactly
60/// `<FieldType as Recallable>::Memento`. The macro does not prescribe one canonical container
61/// semantics; it uses whatever memento shape the field type defines.
62///
63/// The companion struct itself is generated as an internal implementation detail. The supported
64/// way to name it is `<Struct as Recallable>::Memento`. It is intended to be produced and consumed
65/// alongside the source struct, primarily through `Recall::recall`/`TryRecall::try_recall`, not as
66/// a field-inspection surface with widened visibility.
67///
68/// The `Recallable` impl sets `type Memento` to that generated type and adds any required generic
69/// bounds.
70///
71/// The generated memento struct always derives `Clone`, `Debug`, and `PartialEq`.
72/// When the `serde` feature is enabled, it also derives `serde::Deserialize`.
73/// All non-skipped field types must implement these derived traits.
74///
75/// To suppress the default `Clone`, `Debug`, and `PartialEq` derives (and their
76/// corresponding trait bounds), annotate the struct with
77/// `#[recallable(skip_memento_default_derives)]`. When serde is enabled, `Deserialize` is
78/// still derived on the memento even with this attribute.
79///
80/// When the `impl_from` feature is enabled for the macro crate, a
81/// `From<Struct>` implementation is also generated for the memento type. For `#[recallable]`
82/// fields, that additionally requires `<FieldType as Recallable>::Memento: From<FieldType>`.
83#[proc_macro_derive(Recallable, attributes(recallable))]
84pub fn derive_recallable(input: TokenStream) -> TokenStream {
85    let input: DeriveInput = parse_macro_input!(input as DeriveInput);
86    let ir = match context::StructIr::analyze(&input) {
87        Ok(ir) => ir,
88        Err(e) => return e.to_compile_error().into(),
89    };
90    let env = context::CodegenEnv::resolve();
91
92    let memento_struct = context::gen_memento_struct(&ir, &env);
93    let recallable_impl = context::gen_recallable_impl(&ir, &env);
94    let from_impl = if context::IMPL_FROM_ENABLED {
95        let from_impl = context::gen_from_impl(&ir, &env);
96        quote! {
97            #[automatically_derived]
98            #from_impl
99        }
100    } else {
101        quote! {}
102    };
103
104    let output = quote! {
105        const _: () = {
106            #[automatically_derived]
107            #memento_struct
108
109            #[automatically_derived]
110            #recallable_impl
111
112            #from_impl
113        };
114    };
115    output.into()
116}
117
118#[proc_macro_derive(Recall, attributes(recallable))]
119/// Derive macro that generates the `Recall` trait implementation.
120///
121/// The generated `recall` method:
122/// - assigns fields directly by default,
123/// - recursively calls `recall` on fields marked with `#[recallable]`,
124/// - respects `#[recallable(skip)]` by omitting those fields from recalling.
125///
126/// For `#[recallable]` fields, replace/merge behavior comes from the field type's own
127/// `Recall` implementation.
128pub fn derive_recall(input: TokenStream) -> TokenStream {
129    let input: DeriveInput = parse_macro_input!(input as DeriveInput);
130    let ir = match context::StructIr::analyze(&input) {
131        Ok(ir) => ir,
132        Err(e) => return e.to_compile_error().into(),
133    };
134    let env = context::CodegenEnv::resolve();
135
136    let recall_impl = context::gen_recall_impl(&ir, &env);
137
138    let output = quote! {
139        const _: () = {
140            #[automatically_derived]
141            #recall_impl
142        };
143    };
144    output.into()
145}