tinc_derive/
message_tracker.rs1use 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}