Skip to main content

tracker_macros/
lib.rs

1//! Macros for the `tracker` crate.
2
3#![warn(
4    missing_debug_implementations,
5    missing_docs,
6    rust_2018_idioms,
7    unreachable_pub,
8    clippy::cargo,
9    clippy::must_use_candidate
10)]
11
12use proc_macro::{self, Span, TokenStream};
13use proc_macro2::{Span as Span2, TokenStream as TokenStream2};
14use quote::{quote, quote_spanned, ToTokens};
15use syn::{
16    parse_macro_input, Attribute, Error, Field, Fields, GenericParam, Ident, ItemStruct, Type,
17};
18
19const NO_EQ: &str = "no_eq";
20const DO_NOT_TRACK: &str = "do_not_track";
21
22/// Implements tracker methods for structs.
23#[proc_macro_attribute]
24pub fn track(attr: TokenStream, item: TokenStream) -> TokenStream {
25    if !attr.is_empty() {
26        return Error::new(
27            attr.into_iter().next().unwrap().span().into(),
28            "This macro doesn't handle attributes",
29        )
30        .into_compile_error()
31        .into();
32    }
33
34    let mut data: ItemStruct = parse_macro_input!(item);
35    let ident = data.ident.clone();
36    let tracker_ty;
37    let struct_vis = &data.vis;
38    let where_clause = &data.generics.where_clause;
39
40    // Remove default type parameters (like <Type=DefaultType>).
41    let mut generics = data.generics.clone();
42    for param in generics.params.iter_mut() {
43        if let GenericParam::Type(ty) = param {
44            ty.eq_token = None;
45            ty.default = None;
46        }
47    }
48
49    let mut generics_iter = data.generics.params.iter();
50    let mut generic_idents = TokenStream2::new();
51
52    if let Some(first) = generics_iter.next() {
53        impl_struct_generics(first, &mut generic_idents);
54        for generic_param in generics_iter {
55            generic_idents.extend(quote! {,});
56            impl_struct_generics(generic_param, &mut generic_idents);
57        }
58    }
59
60    let mut field_list = Vec::new();
61    if let Fields::Named(named_fields) = &mut data.fields {
62        for field in &mut named_fields.named {
63            let (do_not_track, no_eq) = parse_field_attrs(&mut field.attrs);
64            if !do_not_track {
65                let ident = field.ident.clone().expect("Field has no identifier");
66                let ty: Type = field.ty.clone();
67                field_list.push((ident, ty, no_eq, field.vis.clone()));
68            }
69        }
70
71        tracker_ty = tracker_type(field_list.len());
72        let change_field = Field {
73            attrs: Vec::new(),
74            vis: syn::Visibility::Inherited,
75            mutability: syn::FieldMutability::None,
76            ident: Some(Ident::new("tracker", Span::call_site().into())),
77            colon_token: None,
78            ty: Type::Verbatim(tracker_ty.clone()),
79        };
80
81        named_fields.named.push(change_field);
82    } else {
83        panic!("No named fields");
84    }
85
86    let mut output = data.to_token_stream();
87
88    let mut methods = proc_macro2::TokenStream::new();
89    for (num, (id, ty, no_eq, vis)) in field_list.iter().enumerate() {
90        let id_span: Span2 = id.span().unwrap().into();
91
92        let get_id = Ident::new(&format!("get_{}", id), id_span);
93        let get_mut_id = Ident::new(&format!("get_mut_{}", id), id_span);
94        let update_id = Ident::new(&format!("update_{}", id), id_span);
95        let changed_id = Ident::new(&format!("changed_{}", id), id_span);
96        let set_id = Ident::new(&format!("set_{}", id), id_span);
97
98        let get_doc = format!("Get an immutable reference to the {id} field.");
99        let get_mut_doc =
100            format!("Get a mutable reference to the {id} field and mark the field as changed.");
101        let update_doc =
102            format!("Use a closure to update the {id} field and mark the field as changed.");
103        let changed_doc = format!("Check if value of {id} field has changed.");
104        let bit_mask_doc = format!("Get a bit mask to look for changes on the {id} field.");
105
106        methods.extend(quote_spanned! { id_span =>
107            #[allow(dead_code, non_snake_case)]
108            #[must_use]
109            #[doc = #get_doc]
110            #vis fn #get_id(&self) -> &#ty {
111                &self.#id
112            }
113
114            #[allow(dead_code, non_snake_case)]
115            #[must_use]
116            #[doc = #get_mut_doc]
117            #vis fn #get_mut_id(&mut self) -> &mut #ty {
118                self.tracker |= Self::#id();
119                &mut self.#id
120            }
121
122            #[allow(dead_code, non_snake_case)]
123            #[doc = #update_doc]
124            #vis fn #update_id<F: FnOnce(&mut #ty)>(&mut self, f: F) {
125                self.tracker |= Self::#id();
126                f(&mut self.#id);
127            }
128
129            #[allow(dead_code, non_snake_case)]
130            #[doc = #changed_doc]
131            #vis fn #changed_id(&self) -> bool {
132                self.changed(Self::#id())
133            }
134
135            #[allow(dead_code, non_snake_case)]
136            #[must_use]
137            #[doc = #bit_mask_doc]
138            #vis fn #id() -> #tracker_ty {
139                1 << #num
140            }
141        });
142
143        if *no_eq {
144            let set_doc = format!("Set the value of field {id} and mark the field as changed.");
145            methods.extend(quote_spanned! { id_span =>
146                #[allow(dead_code, non_snake_case)]
147                #[doc = #set_doc]
148                #vis fn #set_id(&mut self, value: #ty) {
149                    self.tracker |= Self::#id();
150                    self.#id = value;
151                }
152            });
153        } else {
154            let set_doc = format!("Set the value of field {id} and mark the field as changed if it's not equal to the previous value.");
155            methods.extend(quote_spanned! { id_span =>
156                #[allow(dead_code, non_snake_case)]
157                #[doc = #set_doc]
158                #vis fn #set_id(&mut self, value: #ty) {
159                    if self.#id != value {
160                        self.tracker |= Self::#id();
161                    }
162                    self.#id = value;
163                }
164            });
165        }
166    }
167
168    output.extend(quote_spanned! { ident.span() =>
169        impl #generics #ident < #generic_idents > #where_clause {
170            #methods
171            #[allow(dead_code)]
172            #[must_use]
173            /// Get a bit mask to look for changes on all fields.
174            #struct_vis fn track_all() -> #tracker_ty {
175                #tracker_ty::MAX
176            }
177
178            #[allow(dead_code)]
179            /// Mark all fields of the struct as changed.
180            #struct_vis fn mark_all_changed(&mut self) {
181                self.tracker = #tracker_ty::MAX;
182            }
183
184            /// Check for changes made to this struct with a given bitmask.
185            ///
186            /// To receive the bitmask, simply call `Type::#field_name()`
187            /// or `Type::#track_all()`.
188            #[warn(dead_code)]
189            #[must_use]
190            #struct_vis fn changed(&self, mask: #tracker_ty) -> bool {
191                self.tracker & mask != 0
192            }
193
194            /// Check for any changes made to this struct.
195            #[allow(dead_code)]
196            #[must_use]
197            #struct_vis fn changed_any(&self) -> bool {
198                self.tracker != 0
199            }
200
201            /// Resets the tracker value of this struct to mark all fields
202            /// as unchanged again.
203            #[warn(dead_code)]
204            #struct_vis fn reset(&mut self) {
205                self.tracker = 0;
206            }
207        }
208    });
209
210    output.into()
211}
212
213fn impl_struct_generics(param: &GenericParam, stream: &mut TokenStream2) {
214    match param {
215        GenericParam::Type(ty) => ty.ident.to_tokens(stream),
216        GenericParam::Const(cnst) => cnst.to_tokens(stream),
217        GenericParam::Lifetime(lifetime) => lifetime.to_tokens(stream),
218    }
219}
220
221/// Look for no_eq and do_not_track attributes and remove
222/// them from the tokens.
223fn parse_field_attrs(attrs: &mut Vec<Attribute>) -> (bool, bool) {
224    let mut do_not_track = false;
225    let mut no_eq = false;
226    let attrs_clone = attrs.clone();
227
228    for (index, attr) in attrs_clone.iter().enumerate() {
229        let segs = &attr.path().segments;
230        match segs.len() {
231            1 => {
232                let first = &segs.first().unwrap().ident;
233                if first == NO_EQ {
234                    attrs.remove(index);
235                    no_eq = true;
236                } else if first == DO_NOT_TRACK {
237                    attrs.remove(index);
238                    do_not_track = true;
239                }
240            }
241            2 => {
242                let mut iter = segs.iter();
243                let first = &iter.next().unwrap().ident;
244                if first == "tracker" {
245                    let second = &iter.next().unwrap().ident;
246                    if second == NO_EQ {
247                        attrs.remove(index);
248                        no_eq = true;
249                    } else if second == DO_NOT_TRACK {
250                        attrs.remove(index);
251                        do_not_track = true;
252                    }
253                }
254            }
255            _ => {}
256        }
257    }
258
259    (do_not_track, no_eq)
260}
261
262fn tracker_type(len: usize) -> proc_macro2::TokenStream {
263    match len {
264        0..=8 => {
265            quote! {u8}
266        }
267        9..=16 => {
268            quote! {u16}
269        }
270        17..=32 => {
271            quote! {u32}
272        }
273        33..=64 => {
274            quote! {u64}
275        }
276        65..=128 => {
277            quote! {u128}
278        }
279        _ => {
280            panic!("You can only track up to 128 values")
281        }
282    }
283}