ters_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{parse_macro_input, ItemStruct};
4
5/// Generate getters and setters procedurally.
6///
7/// Annotate fields with `#[get]` to generate a getter method.
8/// ```ignore
9/// use ters::ters;
10///
11/// #[ters]
12/// struct Foo {
13///     a: i32,
14///     #[get]
15///     b: bool,
16/// }
17///
18/// fn getters() {
19///     let foo = Foo { a: 42, b: true };
20///     assert_eq!(foo.b(), &true);
21/// }
22/// ```
23///
24/// Annotate fields with `#[set]` to generate a setter method.
25/// ```ignore
26/// use ters::ters;
27///
28/// #[ters]
29/// struct Foo {
30///     #[set]
31///     a: i32,
32///     b: bool,
33/// }
34///
35/// fn setters() {
36///     let mut foo = Foo { a: 42, b: true };
37///     foo.set_a(31);
38/// }
39/// ```
40///
41/// Annotate fields with `#[get]` and `#[set]` to generate both a getter and a setter method.
42/// ```ignore
43/// use ters::ters;
44///
45/// #[ters]
46/// struct Foo {
47///     #[get]
48///     #[set]
49///     a: i32,
50///     b: bool,
51/// }
52///
53/// fn getters_and_setters() {
54///     let mut foo = Foo { a: 42, b: true };
55///     assert_eq!(foo.a(), &42);
56///     foo.set_a(31);
57///
58///     assert_eq!(foo.a(), &31);
59/// }
60/// ```
61///
62/// Unannotated fields will not have generated getters or setters.
63/// ```ignore
64/// use ters::ters;
65///
66/// #[ters]
67/// struct Foo {
68///     a: i32,
69///     #[get]
70///     b: bool,
71/// }
72///
73/// fn getters_not_generated() {
74///     let foo = Foo { a: 42, b: true };
75///     assert_eq!(foo.a(), &42); // this method doesn't exist
76/// }
77/// ```
78#[proc_macro_attribute]
79pub fn ters(_args: TokenStream, tokens: TokenStream) -> TokenStream {
80    let item = parse_macro_input!(tokens as ItemStruct);
81
82    ters_inner(item).into()
83}
84
85fn ters_inner(mut item: ItemStruct) -> proc_macro2::TokenStream {
86    let (impl_generics, ty_generics, where_clause) = item.generics.split_for_impl();
87
88    let mut fields = Vec::new();
89
90    for field in item.fields.iter_mut() {
91        let mut get = false;
92        let mut set = false;
93
94        field.attrs.retain(|attr| {
95            if attr.path().is_ident("get") {
96                get = true;
97                false
98            } else if attr.path().is_ident("set") {
99                set = true;
100                false
101            } else {
102                true
103            }
104        });
105
106        fields.push((
107            field.ident.clone().unwrap(),
108            field.ty.clone(),
109            get,
110            set,
111            field
112                .attrs
113                .iter()
114                .filter(|attr| {
115                    attr.path()
116                        .get_ident()
117                        .map(|ident| ident == "doc")
118                        .is_some_and(|is_doc| is_doc)
119                })
120                .cloned()
121                .collect::<Vec<_>>(),
122        ));
123    }
124
125    let accessors = fields
126        .iter()
127        .filter_map(|(ident, ty, get, set, docs)| {
128            let set_ident = format_ident!("set_{ident}");
129            let str_ident = ident.to_string();
130
131            let mut body = quote! {};
132
133            if *get {
134                body.extend(quote! {
135                    #[doc = "Getter for `"]
136                    #[doc = #str_ident]
137                    #[doc = "`.\n\n"]
138                    #(#docs)*
139                    #[inline]
140                    pub fn #ident(&self) -> &#ty {
141                        &self.#ident
142                    }
143                });
144            }
145
146            if *set {
147                body.extend(quote! {
148                    #[doc = "Setter for `"]
149                    #[doc = #str_ident]
150                    #[doc = "`.\n\n"]
151                    #(#docs)*
152                    #[inline]
153                    pub fn #set_ident(&mut self, value: #ty) {
154                        self.#ident = value;
155                    }
156                });
157            }
158
159            (!body.is_empty()).then_some(body)
160        })
161        .collect::<Vec<_>>();
162
163    let ident = &item.ident;
164
165    let impl_ = (!accessors.is_empty()).then_some(quote! {
166        impl #impl_generics #ident #ty_generics #where_clause {
167            #(
168                #accessors
169            )*
170        }
171    });
172
173    quote! {
174        #item
175        #impl_
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use quote::quote;
182    use syn::parse_quote;
183
184    use crate::ters_inner;
185
186    #[test]
187    fn docs() {
188        let input = parse_quote! {
189            struct Foo {
190                /// Baz.
191                #[get]
192                bar: u8,
193            }
194        };
195
196        let expected = quote! {
197            struct Foo {
198                /// Baz.
199                bar: u8,
200            }
201
202            impl Foo {
203                #[doc = "Getter for `"]
204                #[doc = "bar"]
205                #[doc = "`.\n\n"]
206                /// Baz.
207                #[inline]
208                pub fn bar(&self) -> &u8 {
209                    &self.bar
210                }
211            }
212        };
213
214        let out: proc_macro2::TokenStream = ters_inner(input).into();
215
216        assert_eq!(out.to_string(), expected.to_string());
217    }
218}