utils_accessors_derive/
lib.rs1use proc_macro::TokenStream;
7use quote::{format_ident, quote};
8use syn::{Data, DeriveInput, Fields, LitBool, parse_macro_input, spanned::Spanned};
9
10#[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 let _ = attr.parse_nested_meta(|meta| {
115 if meta.path.is_ident("skip") {
116 if meta.input.is_empty() {
117 skip = true; } else if let Ok(v) = meta.value()?.parse::<LitBool>() {
119 if v.value {
120 skip = true;
121 } }
123 }
124 Ok(())
125 });
126 }
127 skip
128}