tinc_derive/
message_tracker.rs

1use proc_macro2::TokenStream;
2use quote::quote;
3use syn::spanned::Spanned;
4
5struct TincContainerOptions {
6    pub crate_path: syn::Path,
7    pub tagged: bool,
8    pub with_non_finite_values: bool,
9}
10
11impl TincContainerOptions {
12    fn from_attributes<'a>(attrs: impl IntoIterator<Item = &'a syn::Attribute>) -> syn::Result<Self> {
13        let mut crate_ = None;
14        let mut tagged = false;
15        let mut with_non_finite_values = false;
16
17        for attr in attrs {
18            let syn::Meta::List(list) = &attr.meta else {
19                continue;
20            };
21
22            if list.path.is_ident("tinc") {
23                list.parse_nested_meta(|meta| {
24                    if meta.path.is_ident("crate") {
25                        if crate_.is_some() {
26                            return Err(meta.error("crate option already set"));
27                        }
28
29                        let _: syn::token::Eq = meta.input.parse()?;
30                        let path: syn::LitStr = meta.input.parse()?;
31                        crate_ = Some(syn::parse_str(&path.value())?);
32                    } else if meta.path.is_ident("tagged") {
33                        tagged = true;
34                    } else if meta.path.is_ident("with_non_finite_values") {
35                        with_non_finite_values = true;
36                    } else {
37                        return Err(meta.error("unsupported attribute"));
38                    }
39
40                    Ok(())
41                })?;
42            }
43        }
44
45        let mut options = TincContainerOptions::default();
46        if let Some(crate_) = crate_ {
47            options.crate_path = crate_;
48        }
49
50        if tagged {
51            options.tagged = true;
52        }
53        if with_non_finite_values {
54            options.with_non_finite_values = true;
55        }
56
57        Ok(options)
58    }
59}
60
61impl Default for TincContainerOptions {
62    fn default() -> Self {
63        Self {
64            crate_path: syn::parse_str("::tinc").unwrap(),
65            tagged: false,
66            with_non_finite_values: false,
67        }
68    }
69}
70
71#[derive(Default)]
72struct TincFieldOptions {
73    pub enum_path: Option<syn::Path>,
74    pub oneof: bool,
75    pub with_non_finite_values: bool,
76}
77
78impl TincFieldOptions {
79    fn from_attributes<'a>(attrs: impl IntoIterator<Item = &'a syn::Attribute>) -> syn::Result<Self> {
80        let mut enum_ = None;
81        let mut oneof = false;
82        let mut with_non_finite_values = false;
83
84        for attr in attrs {
85            let syn::Meta::List(list) = &attr.meta else {
86                continue;
87            };
88
89            if list.path.is_ident("tinc") {
90                list.parse_nested_meta(|meta| {
91                    if meta.path.is_ident("enum") {
92                        if enum_.is_some() {
93                            return Err(meta.error("enum option already set"));
94                        }
95
96                        let _: syn::token::Eq = meta.input.parse()?;
97                        let path: syn::LitStr = meta.input.parse()?;
98                        enum_ = Some(syn::parse_str(&path.value())?);
99                    } else if meta.path.is_ident("oneof") {
100                        oneof = true;
101                    } else if meta.path.is_ident("with_non_finite_values") {
102                        with_non_finite_values = true;
103                    } else {
104                        return Err(meta.error("unsupported attribute"));
105                    }
106
107                    Ok(())
108                })?;
109            }
110        }
111
112        let mut options = TincFieldOptions::default();
113        if let Some(enum_) = enum_ {
114            options.enum_path = Some(enum_);
115        }
116
117        if oneof {
118            options.oneof = true;
119        }
120        if with_non_finite_values {
121            options.with_non_finite_values = true;
122        }
123
124        Ok(options)
125    }
126}
127
128pub(crate) fn derive_message_tracker(input: TokenStream) -> TokenStream {
129    let input = match syn::parse2::<syn::DeriveInput>(input) {
130        Ok(input) => input,
131        Err(e) => return e.to_compile_error(),
132    };
133
134    let opts = match TincContainerOptions::from_attributes(&input.attrs) {
135        Ok(options) => options,
136        Err(e) => return e.to_compile_error(),
137    };
138
139    match &input.data {
140        syn::Data::Struct(data) => derive_message_tracker_struct(input.ident, opts, data),
141        syn::Data::Enum(data) => derive_message_tracker_enum(input.ident, opts, data),
142        _ => syn::Error::new(input.span(), "Tracker can only be derived for structs or enums").into_compile_error(),
143    }
144}
145
146fn derive_message_tracker_struct(ident: syn::Ident, opts: TincContainerOptions, data: &syn::DataStruct) -> TokenStream {
147    let TincContainerOptions {
148        crate_path,
149        tagged,
150        with_non_finite_values,
151    } = opts;
152    if tagged {
153        return syn::Error::new(ident.span(), "tagged can only be used on enums").into_compile_error();
154    }
155    if with_non_finite_values {
156        return syn::Error::new(ident.span(), "with_non_finite_values can only be used on floats").into_compile_error();
157    }
158
159    let syn::Fields::Named(fields) = &data.fields else {
160        return syn::Error::new(ident.span(), "Tracker can only be derived for structs with named fields")
161            .into_compile_error();
162    };
163
164    let tracker_ident = syn::Ident::new(&format!("{ident}Tracker"), ident.span());
165
166    let struct_fields = fields
167        .named
168        .iter()
169        .map(|f| {
170            let field_ident = f.ident.as_ref().expect("field must have an identifier");
171            let ty = &f.ty;
172
173            let TincFieldOptions {
174                enum_path,
175                oneof,
176                with_non_finite_values,
177            } = TincFieldOptions::from_attributes(&f.attrs)?;
178
179            if enum_path.is_some() && (oneof || with_non_finite_values) {
180                return Err(syn::Error::new(
181                    f.span(),
182                    "enum cannot be set with oneof or with_non_finite_values",
183                ));
184            }
185            if oneof && with_non_finite_values {
186                return Err(syn::Error::new(f.span(), "oneof cannot be set with with_non_finite_values"));
187            }
188
189            let ty = match enum_path {
190                Some(enum_path) => quote! { <#ty as #crate_path::__private::EnumHelper>::Target<#enum_path> },
191                None if oneof => quote! { <#ty as #crate_path::__private::OneOfHelper>::Target },
192                None if with_non_finite_values => {
193                    quote! { <#ty as #crate_path::__private::FloatWithNonFinDesHelper>::Target }
194                }
195                None => quote! { #ty },
196            };
197
198            Ok(quote! {
199                pub #field_ident: Option<<#ty as #crate_path::__private::TrackerFor>::Tracker>
200            })
201        })
202        .collect::<syn::Result<Vec<_>>>();
203
204    let struct_fields = match struct_fields {
205        Ok(fields) => fields,
206        Err(e) => return e.to_compile_error(),
207    };
208
209    quote! {
210        #[allow(clippy::all, dead_code, unused_imports, unused_variables, unused_parens)]
211        const _: () = {
212            #[derive(Debug, Default)]
213            pub struct #tracker_ident {
214                #(#struct_fields),*
215            }
216
217            impl #crate_path::__private::Tracker for #tracker_ident {
218                type Target = #ident;
219
220                #[inline(always)]
221                fn allow_duplicates(&self) -> bool {
222                    true
223                }
224            }
225
226            impl #crate_path::__private::TrackerFor for #ident {
227                type Tracker = #crate_path::__private::StructTracker<#tracker_ident>;
228            }
229        };
230    }
231}
232
233fn derive_message_tracker_enum(ident: syn::Ident, opts: TincContainerOptions, data: &syn::DataEnum) -> TokenStream {
234    let TincContainerOptions {
235        crate_path,
236        tagged,
237        with_non_finite_values,
238    } = opts;
239    let tracker_ident = syn::Ident::new(&format!("{ident}Tracker"), ident.span());
240
241    if with_non_finite_values {
242        return syn::Error::new(ident.span(), "with_non_finite_values can only be used on floats").into_compile_error();
243    }
244
245    let variants = data
246        .variants
247        .iter()
248        .map(|v| {
249            let variant_ident = &v.ident;
250            let syn::Fields::Unnamed(unnamed) = &v.fields else {
251                return Err(syn::Error::new(
252                    v.span(),
253                    "Tracker can only be derived for enums with unnamed variants",
254                ));
255            };
256
257            if unnamed.unnamed.len() != 1 {
258                return Err(syn::Error::new(
259                    v.span(),
260                    "Tracker can only be derived for enums with a single field variants",
261                ));
262            }
263
264            let field = &unnamed.unnamed[0];
265            let ty = &field.ty;
266
267            let TincFieldOptions {
268                enum_path,
269                oneof,
270                with_non_finite_values,
271            } = TincFieldOptions::from_attributes(v.attrs.iter().chain(field.attrs.iter()))?;
272
273            if oneof {
274                return Err(syn::Error::new(
275                    v.span(),
276                    "oneof can only be used on struct fields, not on enum variants",
277                ));
278            }
279
280            let ty = match enum_path {
281                Some(enum_path) => quote! {
282                    <#ty as #crate_path::__private::EnumHelper>::Target<#enum_path>
283                },
284                None if with_non_finite_values => quote! {
285                    <#ty as #crate_path::__private::FloatWithNonFinDesHelper>::Target
286                },
287                None => quote! {
288                    #ty
289                },
290            };
291
292            Ok((
293                quote! {
294                    #variant_ident(<#ty as #crate_path::__private::TrackerFor>::Tracker)
295                },
296                quote! {
297                    #variant_ident
298                },
299            ))
300        })
301        .collect::<syn::Result<(Vec<_>, Vec<_>)>>();
302
303    let (variants, variant_idents) = match variants {
304        Ok(variants) => variants,
305        Err(e) => return e.to_compile_error(),
306    };
307
308    let tracker = if tagged {
309        quote! {
310            #crate_path::__private::TaggedOneOfTracker<#tracker_ident>
311        }
312    } else {
313        quote! {
314            #crate_path::__private::OneOfTracker<#tracker_ident>
315        }
316    };
317
318    quote! {
319        #[allow(clippy::all, dead_code, unused_imports, unused_variables, unused_parens)]
320        const _: () = {
321            #[derive(std::fmt::Debug)]
322            pub enum #tracker_ident {
323                #(#variants),*
324            }
325
326            impl #crate_path::__private::Tracker for #tracker_ident {
327                type Target = #ident;
328
329                #[inline(always)]
330                fn allow_duplicates(&self) -> bool {
331                    match self {
332                        #(Self::#variant_idents(v) => v.allow_duplicates()),*
333                    }
334                }
335            }
336
337            impl #crate_path::__private::TrackerFor for #ident {
338                type Tracker = #tracker;
339            }
340        };
341    }
342}