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