1extern crate proc_macro;
5
6use proc_macro::TokenStream;
7use proc_macro2::Ident;
8use quote::{format_ident, quote};
9use syn::parse::{Parse, ParseStream};
10use syn::{parse_macro_input, punctuated::Punctuated, DeriveInput, LitInt, Path, Token, Variant};
11
12struct NameInfo {
14 struct_name: Ident,
15 struct_base: Ident,
16 struct_version: u16,
17}
18
19fn rsplit_once<'a>(s: &'a str, delimiter: char) -> Option<(&'a str, &'a str)> {
23 let split_pos = s.rfind(delimiter)?;
24
25 let a = &s[..split_pos];
26 let b = &s[(split_pos + 1)..];
27
28 Some((a, b))
29}
30
31impl NameInfo {
32 fn from_name(ident: &Ident) -> Self {
33 let struct_name = ident.clone();
34 let struct_name_string = struct_name.to_string();
35
36 let (struct_base, struct_version) = match rsplit_once(&struct_name_string, 'V') {
38 Some((base, version)) => {
39 if base.is_empty() {
40 panic!("failed to parse struct name");
41 }
42 let base = Ident::new(&base, ident.span());
43 let version: u16 = version.parse().expect("failed to parse struct version");
44 (base, version)
45 }
46 None => panic!("failed to parse struct name into base+version"),
47 };
48
49 NameInfo {
50 struct_name,
51 struct_base,
52 struct_version,
53 }
54 }
55}
56
57fn versioned_name(base: &Ident, version: u16) -> Ident {
58 let name = format!("{}V{}", base, version);
59 Ident::new(&name, base.span())
60}
61
62#[proc_macro_derive(Versioned)]
65pub fn derive_versioned(input: TokenStream) -> TokenStream {
66 let input = parse_macro_input!(input as DeriveInput);
68
69 let NameInfo {
70 struct_name,
71 struct_base,
72 struct_version,
73 } = NameInfo::from_name(&input.ident);
74
75 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
77
78 let expanded = quote! {
79 #[doc(hidden)]
80 #[allow(
81 non_upper_case_globals,
82 unused_attributes,
83 unused_qualifications,
84 non_camel_case_types,
85 non_snake_case
86 )]
87 const _: () = {
88 #[allow(rust_2018_idioms, clippy::useless_attribute)]
89 extern crate aversion as _aversion;
90
91 #[automatically_derived]
92 impl #impl_generics _aversion::Versioned
93 for #struct_name #ty_generics #where_clause {
94 const VER: u16 = #struct_version;
95 type Base = #struct_base;
96 }
97 };
98 };
99 expanded.into()
101}
102
103#[proc_macro_derive(UpgradeLatest)]
113pub fn derive_upgrade_latest(input: TokenStream) -> TokenStream {
114 let input = parse_macro_input!(input as DeriveInput);
116
117 let NameInfo {
118 struct_name,
119 struct_base,
120 struct_version,
121 } = NameInfo::from_name(&input.ident);
122
123 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
125
126 let all_versions = (1..=struct_version)
128 .into_iter()
129 .map(|ii| (ii, versioned_name(&struct_base, ii)))
130 .collect::<Vec<_>>();
131
132 let read_message_arms = all_versions
134 .iter()
135 .map(|(v, n)| quote_read_message_arm(*v, n, &struct_name));
136
137 let all_hops = (1..struct_version - 1)
140 .into_iter()
141 .map(|ii| quote_from_version_hop(&struct_base, ii, struct_version))
142 .collect::<Vec<_>>();
143
144 let expanded = quote! {
145 #[doc(hidden)]
146 #[allow(
147 non_upper_case_globals,
148 unused_attributes,
149 unused_qualifications,
150 non_camel_case_types,
151 non_snake_case
152 )]
153 const _: () = {
154 #[allow(rust_2018_idioms, clippy::useless_attribute)]
155 extern crate aversion as _aversion;
156
157 #[automatically_derived]
158 impl #impl_generics _aversion::group::UpgradeLatest
159 for #struct_name #ty_generics #where_clause {
160
161 fn upgrade_latest<Src>(src: &mut Src, header: Src::Header) -> ::std::result::Result<Self, Src::Error>
162 where
163 Src: _aversion::group::DataSource,
164 {
165 use _aversion::group::GroupHeader;
166
167 let ver = header.msg_ver();
168 match ver {
169 #(#read_message_arms)*
170
171 _ => Err(src.unknown_version::<#struct_base>(ver)),
172 }
173 }
174 }
175
176 #(#all_hops)*
177 };
178 };
179 expanded.into()
181}
182
183fn quote_read_message_arm(
184 version: u16,
185 versioned_name: &Ident,
186 target_name: &Ident,
187) -> proc_macro2::TokenStream {
188 quote! {
189 #version => {
190 let msg = src.read_message::<#versioned_name>(&header)?;
191 let upgraded = <#target_name as _aversion::FromVersion::<#versioned_name>>::from_version(msg);
192 Ok(upgraded)
193 }
194 }
195}
196
197fn quote_from_version_hop(base: &Ident, lo: u16, hi: u16) -> proc_macro2::TokenStream {
203 assert!(hi > lo);
204 if hi - lo < 2 {
205 return quote! {};
207 }
208
209 fn tmp_ident(x: u16) -> Ident {
211 format_ident!("v{}", x)
212 }
213
214 let upgrade_chain = (lo..hi)
216 .into_iter()
217 .map(|ii| {
218 let jj = ii + 1;
219 let tmp_ii = tmp_ident(ii);
220 let tmp_jj = tmp_ident(jj);
221 let ident_jj = versioned_name(base, jj);
222 quote! {
223 let #tmp_jj = #ident_jj::from_version(#tmp_ii);
224 }
225 })
226 .collect::<Vec<_>>();
227
228 let lo_ident = versioned_name(base, lo);
229 let hi_ident = versioned_name(base, hi);
230 let lo_tmp = tmp_ident(lo);
231 let hi_tmp = tmp_ident(hi);
232
233 quote! {
234 impl FromVersion<#lo_ident> for #hi_ident {
235 fn from_version(#lo_tmp: #lo_ident) -> Self {
236 #(#upgrade_chain)*
237 #hi_tmp
238 }
239 }
240 }
241}
242
243#[proc_macro_derive(GroupDeserialize)]
249pub fn derive_group_deserialize(input: TokenStream) -> TokenStream {
250 let input = parse_macro_input!(input as DeriveInput);
252 let enum_name = &input.ident;
253
254 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
256
257 let source_struct = input.data;
258 let variants = if let syn::Data::Enum(syn::DataEnum { variants, .. }) = source_struct {
259 variants
260 } else {
261 panic!("couldn't find enum variants");
262 };
263
264 let match_arms = variants
265 .iter()
266 .map(|v| {
267 let gv = GroupVariant::from_enum_variant(v);
269 gv.to_match_arm(enum_name)
271 })
272 .collect::<Vec<_>>();
273
274 let expanded = quote! {
275 #[doc(hidden)]
276 #[allow(
277 non_upper_case_globals,
278 unused_attributes,
279 unused_qualifications,
280 non_camel_case_types,
281 non_snake_case
282 )]
283 const _: () = {
284 #[allow(rust_2018_idioms, clippy::useless_attribute)]
285 extern crate aversion as _aversion;
286
287 #[automatically_derived]
288 impl #impl_generics _aversion::GroupDeserialize
289 for #enum_name #ty_generics #where_clause {
290 fn read_message<Src>(src: &mut Src) -> ::std::result::Result<Self, Src::Error>
291 where
292 Src: _aversion::group::DataSource,
293 {
294 use _aversion::{MessageId, group::{GroupHeader, UpgradeLatest}};
295
296 let header = src.read_header()?;
297 match header.msg_id() {
298 #(#match_arms)*
299 _ => {
300 Err(src.unknown_message(header.msg_id()))
301 }
302 }
303 }
304 }
305 };
306 };
307
308 expanded.into()
310}
311
312#[derive(Debug)]
313struct GroupVariant {
314 name: Ident,
315 target: Path,
316}
317
318impl GroupVariant {
319 fn from_enum_variant(variant: &Variant) -> GroupVariant {
320 let name = variant.ident.clone();
321 let mut target_path: Option<Path> = None;
322 if let syn::Fields::Unnamed(syn::FieldsUnnamed {
323 unnamed: variant_fields,
324 ..
325 }) = &variant.fields
326 {
327 if variant_fields.len() != 1 {
328 panic!("enum must contain exactly 1 field");
329 }
330 let field = variant_fields.first().unwrap();
331
332 if let syn::Type::Path(syn::TypePath { path, .. }) = &field.ty {
333 target_path = Some(path.clone());
334 }
335 }
336
337 let target = target_path
338 .unwrap_or_else(|| panic!("failed to extract enum target path for {}", name));
339
340 GroupVariant { name, target }
341 }
342
343 fn to_match_arm(&self, enum_name: &Ident) -> proc_macro2::TokenStream {
344 let enum_variant = &self.name;
345 let struct_name = &self.target;
346
347 quote! {
348 #struct_name::MSG_ID => {
349 let msg = #struct_name::upgrade_latest(src, header)?;
350 Ok(#enum_name::#enum_variant(msg))
351 }
352 }
353 }
354}
355
356#[proc_macro]
368pub fn assign_message_ids(tokens: TokenStream) -> TokenStream {
369 let id_list = parse_macro_input!(tokens as MessageIdList);
370 let id_impls = id_list.to_impl();
371 let expanded = quote! {
372 #[doc(hidden)]
373 #[allow(
374 non_upper_case_globals,
375 unused_attributes,
376 unused_qualifications,
377 non_camel_case_types,
378 non_snake_case
379 )]
380 const _: () = {
381 #[allow(rust_2018_idioms, clippy::useless_attribute)]
382 extern crate aversion as _aversion;
383
384 #id_impls
385 };
386 };
387 expanded.into()
388}
389
390#[derive(Debug, Default)]
391struct MessageIdList {
392 list: Vec<MessageIdValue>,
393}
394
395#[derive(Debug)]
396struct MessageIdValue {
397 name: Path,
398 msg_id: LitInt,
400}
401
402impl Parse for MessageIdValue {
406 fn parse(input: ParseStream) -> syn::parse::Result<Self> {
407 let name: Path = input.parse()?;
408 input.parse::<Token![:]>()?;
409 let msg_id: LitInt = input.parse()?;
410 Ok(MessageIdValue { name, msg_id })
411 }
412}
413
414impl Parse for MessageIdList {
418 fn parse(input: ParseStream) -> syn::parse::Result<Self> {
419 type IdList = Punctuated<MessageIdValue, Token![,]>;
420
421 let id_list = IdList::parse_terminated(input)?;
422
423 Ok(MessageIdList {
424 list: id_list.into_iter().collect(),
425 })
426 }
427}
428
429impl MessageIdList {
431 fn to_impl(&self) -> proc_macro2::TokenStream {
432 let mut tokens = proc_macro2::TokenStream::new();
433 for id_val in &self.list {
434 tokens.extend(id_val.to_impl())
435 }
436 tokens
437 }
438}
439
440impl MessageIdValue {
442 fn to_impl(&self) -> proc_macro2::TokenStream {
443 let name = &self.name;
444 let msg_id = &self.msg_id;
445 quote! {
446 #[automatically_derived]
447 impl _aversion::MessageId for #name {
448 const MSG_ID: u16 = #msg_id;
449 }
450 }
451 }
452}