utils_accessors_derive/
lib.rs

1//! # Accessor Derive
2//!
3//! This crate provides a derive macro for generating setters and getters for
4//! structs.
5
6use proc_macro::TokenStream;
7use quote::{format_ident, quote};
8use syn::{Data, DeriveInput, Fields, LitBool, parse_macro_input, spanned::Spanned};
9
10/// Derive to generate `.set_<field>(&mut self, value: Ty) -> &mut Self` and
11/// `const .with_<field>(mut self, value: Ty) -> Self` for each **named** field.
12///
13/// - Skipping a field: `#[setters(skip)]`
14///
15/// # Example
16///
17/// ```
18/// use utils_accessors_derive::Setters;
19///
20/// #[derive(Setters)]
21/// struct Foo<T> where T: Default {
22///     a: u32,
23///     #[setters(skip)]
24///     _phantom: T,
25/// }
26///
27/// let mut f = Foo { a: 1, _phantom: u8::default() };
28/// f.set_a(10).set_a(11);
29/// let f2 = f.with_a(42);
30/// assert_eq!(f2.a, 42);
31/// ```
32#[proc_macro_derive(Setters, attributes(setters))]
33pub fn derive_generate_setters(input: TokenStream) -> TokenStream {
34    let DeriveInput {
35        ident,
36        generics,
37        data,
38        ..
39    } = parse_macro_input!(input as DeriveInput);
40
41    let fields = match data {
42        Data::Struct(s) => match s.fields {
43            Fields::Named(n) => n.named,
44            Fields::Unnamed(u) => {
45                return syn::Error::new(u.span(), "Setters only supports named fields")
46                    .to_compile_error()
47                    .into();
48            }
49            Fields::Unit => {
50                return syn::Error::new(
51                    ident.span(),
52                    "GenerateSetters does not apply to unit structs",
53                )
54                .to_compile_error()
55                .into();
56            }
57        },
58        _ => {
59            return syn::Error::new(
60                ident.span(),
61                "GenerateSetters can only be derived for structs",
62            )
63            .to_compile_error()
64            .into();
65        }
66    };
67
68    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
69
70    let mut methods = Vec::new();
71
72    for field in fields {
73        let Some(fname) = &field.ident else { continue };
74        if should_skip(&field.attrs) {
75            continue;
76        }
77
78        let ty = &field.ty;
79        let set_name = format_ident!("set_{}", fname);
80        let with_name = format_ident!("with_{}", fname);
81
82        methods.push(quote! {
83            #[inline]
84            pub fn #set_name(&mut self, value: #ty) -> &mut Self {
85                self.#fname = value;
86                self
87            }
88
89            #[inline]
90            pub const fn #with_name(mut self, value: #ty) -> Self {
91                self.#fname = value;
92                self
93            }
94        });
95    }
96
97    let expanded = quote! {
98        impl #impl_generics #ident #ty_generics #where_clause {
99            #(#methods)*
100        }
101    };
102
103    TokenStream::from(expanded)
104}
105
106fn should_skip(attrs: &[syn::Attribute]) -> bool {
107    let mut skip = false;
108    for attr in attrs {
109        if !attr.path().is_ident("setters") {
110            continue;
111        }
112
113        // Accept #[setters(skip)] and #[setters(skip = true)]
114        let _ = attr.parse_nested_meta(|meta| {
115            if meta.path.is_ident("skip") {
116                if meta.input.is_empty() {
117                    skip = true; // #[setters(skip)]
118                } else if let Ok(v) = meta.value()?.parse::<LitBool>() {
119                    if v.value {
120                        skip = true;
121                    } // #[setters(skip = true)]
122                }
123            }
124            Ok(())
125        });
126    }
127    skip
128}