Skip to main content

pyro_macro/format/
mod.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::{
4    Ident, ItemStruct, Meta, Path, Token,
5    parse::{Parse, ParseStream},
6    punctuated::Punctuated,
7    token::Comma,
8};
9
10/// Defines the documentation requirements for the configuration struct.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum DocRec {
13    /// No documentation required.
14    NoReq,
15    /// The top-level struct must be documented.
16    StructDoc,
17    /// The struct and all its fields must be documented.
18    AllDoc,
19}
20
21impl DocRec {
22    pub fn need_struct(&self) -> bool {
23        match self {
24            DocRec::NoReq => false,
25            DocRec::StructDoc => true,
26            DocRec::AllDoc => true,
27        }
28    }
29}
30
31/// Whitelist of derives that are safe to forward to the rkyv `Archived` type.
32const DERIVE_WHITELIST: &[&str] = &["Debug", "PartialEq", "Eq", "PartialOrd", "Ord"];
33
34/// Parsed arguments for the `#[bridgeable(...)]` attribute macro.
35///
36/// Supports the following syntax:
37///
38/// ```text
39/// #[bridgeable]                                    // defaults
40/// #[bridgeable(Document)]                          // enable Documented impl
41/// #[bridgeable(derive(Debug, PartialEq))]          // forward derives
42/// #[bridgeable(derive(Debug, PartialEq), Document)] // both
43/// ```
44///
45/// - `derive(...)` — derives to forward to the archived type (whitelisted to
46///   Debug, PartialEq, Eq, PartialOrd, Ord). PartialEq and PartialOrd also
47///   generate `#[rkyv(compare(...))]` attributes.
48/// - `Document` — opt-in flag that generates a `Documented` impl producing a
49///   JSON type spec for FFI/RPC consumers.
50pub struct BridgeableArgs {
51    /// Whitelisted derives to pass through to the rkyv `Archived` type.
52    pub derives_to_pass: Vec<Ident>,
53    /// Comparison traits (PartialEq / PartialOrd) found in the derives —
54    /// these also become `#[rkyv(compare(...))]` entries.
55    pub compares_to_add: Vec<Ident>,
56    pub doc_rec: DocRec,
57}
58
59impl BridgeableArgs {
60    /// Build from an already-parsed `Punctuated<Meta, Comma>` list.
61    /// This is the shared logic used by both `Parse` and a direct call site.
62    pub fn from_metas(metas: Punctuated<Meta, Comma>) -> syn::Result<Self> {
63        let mut derives_to_pass = Vec::new();
64        let mut compares_to_add = Vec::new();
65
66        for meta in metas {
67            match &meta {
68                // derive(Debug, PartialEq, ...)
69                Meta::List(list) if list.path.is_ident("derive") => {
70                    let nested = list
71                        .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
72                        .unwrap_or_default();
73
74                    for nested_meta in nested {
75                        if let Meta::Path(path) = nested_meta {
76                            if let Some(ident) = path.get_ident() {
77                                let s = ident.to_string();
78
79                                if DERIVE_WHITELIST.contains(&s.as_str()) {
80                                    derives_to_pass.push(ident.clone());
81                                }
82
83                                if s == "PartialEq" || s == "PartialOrd" {
84                                    compares_to_add.push(ident.clone());
85                                }
86                            }
87                        }
88                    }
89                }
90                other => {
91                    return Err(syn::Error::new_spanned(
92                        other,
93                        "unexpected bridgeable argument; expected `derive(...)` or `Document`",
94                    ));
95                }
96            }
97        }
98
99        Ok(BridgeableArgs {
100            derives_to_pass,
101            compares_to_add,
102            doc_rec: DocRec::NoReq,
103        })
104    }
105}
106
107/// Allows `parse_macro_input!(args as BridgeableArgs)` in proc-macro crates.
108impl Parse for BridgeableArgs {
109    fn parse(input: ParseStream) -> syn::Result<Self> {
110        let metas = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
111        Self::from_metas(metas)
112    }
113}
114
115pub mod bridgeable;
116pub mod deep_ref;
117pub mod documentation;
118pub mod from_row;
119pub mod library;
120pub mod to_row;
121
122pub fn magma(
123    args: BridgeableArgs,
124    item: &mut ItemStruct,
125    import_location: &Path,
126) -> syn::Result<TokenStream> {
127    // 1. Generate Documentation (takes &Item, &Path)
128    let documentation =
129        documentation::generate_documented_impl(&item, &import_location, args.doc_rec)?;
130    let ref_documentation =
131        documentation::ref_documentation(&item, &import_location, args.doc_rec)?;
132
133    // 2. Generate Bridgeable
134    // Note: This consumes 'args' and 'item', so we use clones for the others.
135    let bridge_tokens = bridgeable::bridgeable(&args, item, import_location)?;
136
137    // 3. Generate DeepRef
138    let deep_ref = deep_ref::deep_ref(&item, import_location, &args.derives_to_pass)?;
139    let deep_ref_rkyv = deep_ref::deep_ref_rkyv(&item, import_location)?;
140
141    // 4. Generate FromRow
142    let from_row = from_row::from_row(&item, import_location)?;
143    let ref_from_row = from_row::ref_from_row(&item, import_location)?;
144
145    // 5. Generate ToRow
146    let to_row = to_row::to_row(&item, import_location)?;
147
148    Ok(quote! {
149        #documentation
150        #ref_documentation
151        #bridge_tokens
152        #deep_ref
153        #deep_ref_rkyv
154        #from_row
155        #ref_from_row
156        #to_row
157    })
158}