1use proc_macro2::TokenStream;
2use quote::{format_ident, quote, quote_spanned, ToTokens, TokenStreamExt};
3use syn::{
4 parse_macro_input, spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DataUnion,
5 DeriveInput, Field, Ident, Meta, Type, Variant,
6};
7
8#[proc_macro_derive(Devolve, attributes(devo))]
9pub fn devolve_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
10 let ast = parse_macro_input!(input as DeriveInput);
11 let (vis, name, attrs) = (&ast.vis, &ast.ident, &ast.attrs);
12 let (devo_name, devo_attr) = (format_ident!("Devolved{}", name), format_ident!("devo"));
13
14 let mut serde_attrs = TokenStream::new();
15 let warnings_mod = format_ident!("devolved_{}_warnings", name.to_string().to_lowercase());
16 let devo_fallback_type = attrs.iter().find_map(|attr| match &attr.meta {
17 Meta::List(list) if list.path.get_ident() == Some(&format_ident!("serde")) => {
18 serde_attrs.append_all(quote! { #attr });
19 None
20 }
21 Meta::List(list) if list.path.get_ident() == Some(&devo_attr) => {
22 let mut ft = None;
23 let _ = list.parse_nested_meta(|meta| {
24 let Ok(ty) = meta.value().unwrap().parse::<Type>() else {
25 return Ok(());
26 };
27 ft = Some(ty);
28
29 Ok(())
30 });
31
32 ft
33 }
34 _ => None,
35 });
36
37 #[cfg(feature = "json")]
38 let fallback_type =
39 { devo_fallback_type.unwrap_or(syn::parse2(quote!(serde_json::Value)).unwrap()) };
40
41 #[cfg(not(feature = "json"))]
42 let fallback_type = {
43 use proc_macro2::Span;
44 if let Some(ty) = devo_fallback_type {
45 ty
46 } else {
47 let warning = syn::Error::new(
48 Span::call_site(),
49 "either enable the \"json\" feature or provide the `#[devo(fallback = Type)]` container attribute",
50 )
51 .into_compile_error();
52 return quote! {
53 mod #warnings_mod {
54 #warning
55 }
56 }
57 .into();
58 }
59 };
60
61 let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
62 let (devo_token, (is_tuple_struct, warn, devo_body, evo_impl, devo_impl)): (
63 TokenStream,
64 (
65 bool,
66 Vec<TokenStream>,
67 TokenStream,
68 TokenStream,
69 TokenStream,
70 ),
71 ) = match ast.data {
72 Data::Struct(DataStruct {
73 fields,
74 struct_token,
75 ..
76 }) => (struct_token.into_token_stream(), {
77 let (is_devo, is_named, tokens, evo_impl, devo_impl): (
78 bool,
79 bool,
80 TokenStream,
81 TokenStream,
82 TokenStream,
83 ) = fields.into_iter().enumerate().fold(
84 (
85 false,
86 false,
87 TokenStream::new(),
88 TokenStream::new(),
89 TokenStream::new(),
90 ),
91 |(d, b, mut st, mut evo, mut dvo), (i, f)| {
92 let is_named = f.ident.is_some();
93 let (is_devo, tokens, ev, dv) = if is_named {
94 render_field(
95 name.to_string().to_token_stream(),
96 f,
97 &devo_attr,
98 false,
99 &fallback_type,
100 )
101 } else {
102 render_tuple_field(
103 name.to_string().into_token_stream(),
104 f,
105 &devo_attr,
106 i,
107 None,
108 &fallback_type,
109 )
110 };
111 st.append_all(tokens);
112 evo.append_all(ev);
113 dvo.append_all(dv);
114 (is_devo || d, is_named || b, st, evo, dvo)
115 },
116 );
117
118 let span = name.span();
119 let mut warn = vec![];
120 if !is_devo {
121 warn.push(
122 syn::Error::new(
123 span,
124 "using derive(Devolve) without at least one #[devo] attribute on structs does nothing",
125 )
126 .into_compile_error(),
127 );
128 }
129
130 (
131 !is_named,
132 warn,
133 if is_named {
134 quote! {
135 {
136 #tokens
137 }
138 }
139 } else {
140 quote! {
141 (
142 #tokens
143 )
144 }
145 },
146 if is_named {
147 quote! {
148 {
149 Ok(#name { #evo_impl })
150 }
151 }
152 } else {
153 quote! {
154 (
155 Ok(#name ( #evo_impl ))
156 )
157 }
158 },
159 if is_named {
160 quote! {
161 {
162 #devo_name { #devo_impl }
163 }
164 }
165 } else {
166 quote! {
167 (
168 #devo_name ( #devo_impl )
169 )
170 }
171 },
172 )
173 }),
174
175 Data::Enum(DataEnum {
176 variants,
177 enum_token,
178 ..
179 }) => (enum_token.into_token_stream(), {
180 let (is_untagged, tokens, warn, evo_impl, devo_impl): (
181 bool,
182 TokenStream,
183 Vec<TokenStream>,
184 TokenStream,
185 TokenStream,
186 ) = variants.into_iter().fold(
187 (
188 false,
189 TokenStream::new(),
190 vec![],
191 TokenStream::new(),
192 TokenStream::new(),
193 ),
194 |(is_untagged, mut st, mut w, mut evo, mut dvo), variant| {
195 let (b, tokens, warn, ev, dv) =
196 render_variant(name, &devo_name, variant, &devo_attr, &fallback_type);
197 w.extend(warn);
198 st.append_all(tokens);
199 evo.append_all(dv);
200 dvo.append_all(ev);
201 (b || is_untagged, st, w, evo, dvo)
202 },
203 );
204
205 (
206 false,
207 warn,
208 if is_untagged {
209 quote! {
210 {
211 #tokens
212 }
213 }
214 } else {
215 quote! {
216 {
217 #tokens
218 #[serde(untagged)]
219 UnrecognizedVariant(#fallback_type),
220 }
221 }
222 },
223 quote! {
224 {
225 match self {
226 #evo_impl
227 _ => {
228 let mut e = ::serde_devo::Error::UnknownVariant { ty: "", path: vec![] };
229 Err(e)
230 }
231 }
232 }
233 },
234 quote! {
235 {
236 match self {
237 #devo_impl
238 }
239 }
240 },
241 )
242 }),
243
244 Data::Union(DataUnion { fields, .. }) => {
245 return quote_spanned! {
246 fields.span() => compile_error!("serde-devolve does not support data unions");
247 }
248 .into()
249 }
250 };
251
252 let d = if is_tuple_struct {
253 quote! {
254 #[derive(::serde::Deserialize, ::serde::Serialize)]
255 #serde_attrs
256 #vis #devo_token #devo_name #ty_generics #devo_body #where_clause;
257 }
258 } else {
259 quote! {
260 #[derive(::serde::Deserialize, ::serde::Serialize)]
261 #serde_attrs
262 #vis #devo_token #devo_name #ty_generics #where_clause #devo_body
263 }
264 };
265 quote! {
266 #d
267
268 impl #impl_generics ::serde_devo::Devolve<#fallback_type> for #name #ty_generics #where_clause {
269 type Devolved = #devo_name #ty_generics;
270
271 fn into_devolved(self) -> Self::Devolved {
272 #devo_impl
273 }
274 }
275
276 impl #impl_generics ::serde_devo::Evolve<#fallback_type> for #devo_name #ty_generics #where_clause {
277 type Evolved = #name #ty_generics;
278
279 fn try_into_evolved(self) -> Result<Self::Evolved, ::serde_devo::Error> {
280 #evo_impl
281 }
282 }
283
284 mod #warnings_mod {
285 #(
286 #warn
287 )*
288 }
289 }
290 .into()
291}
292
293fn render_variant(
294 evo_name: &Ident,
295 devo_name: &Ident,
296 Variant {
297 attrs,
298 ident,
299 fields,
300 ..
301 }: Variant,
302 devo_attr: &Ident,
303 fallback_type: &Type,
304) -> (
305 bool,
306 TokenStream,
307 Vec<TokenStream>,
308 TokenStream,
309 TokenStream,
310) {
311 let mut warn = vec![];
312 let is_empty = fields.is_empty();
313 let field_names = fields
314 .iter()
315 .filter_map(|f| f.ident.as_ref())
316 .map(|id| id.to_string())
317 .collect::<Vec<_>>()
318 .join(", ")
319 .parse::<TokenStream>()
320 .unwrap();
321 let field_letters = fields
322 .iter()
323 .scan(b'a', |letter, _| {
324 let l = *letter;
325 *letter += 1;
326 Some(l as char)
327 })
328 .map(|n| n.to_string())
329 .collect::<Vec<_>>();
330 let (is_devo, is_untagged, attrs) = render_attrs(attrs, devo_attr);
331 let (is_named, tokens, e_impl, d_impl): (bool, TokenStream, TokenStream, TokenStream) =
332 fields.into_iter().zip(&field_letters).enumerate().fold(
333 (
334 false,
335 TokenStream::new(),
336 TokenStream::new(),
337 TokenStream::new(),
338 ),
339 |(b, mut st, mut evo, mut dvo), (i, (f, l))| {
340 let is_named = f.ident.is_some();
341 let (_is_devo, tokens, ev, dv) = if is_named {
342 render_field(
343 format!("{evo_name}::{ident}").to_token_stream(),
344 f,
345 devo_attr,
346 true,
347 fallback_type,
348 )
349 } else {
350 render_tuple_field(
351 format!("{evo_name}::{ident}").to_token_stream(),
352 f,
353 devo_attr,
354 i,
355 Some(l),
356 fallback_type,
357 )
358 };
359 st.append_all(tokens);
360 evo.append_all(ev);
361 dvo.append_all(dv);
362 (is_named || b, st, evo, dvo)
363 },
364 );
365
366 let field_letters = field_letters.join(", ").parse::<TokenStream>().unwrap();
367 let tokens = if is_named {
368 quote! {
369 #attrs
370 #ident {
371 #tokens
372 },
373 }
374 } else if is_empty {
375 quote! {
376 #attrs
377 #ident,
378 }
379 } else {
380 quote! {
381 #attrs
382 #ident (
383 #tokens
384 ),
385 }
386 };
387
388 let member = format!("Self::{}", ident).parse::<TokenStream>().unwrap();
389 let evo_member = format!("{}::{}", evo_name, ident)
390 .parse::<TokenStream>()
391 .unwrap();
392 let devo_member = format!("{}::{}", devo_name, ident)
393 .parse::<TokenStream>()
394 .unwrap();
395 let devo_impl = if is_empty {
396 quote! {
397 #member => Ok(#evo_member),
398 }
399 } else if is_named {
400 quote! {
401 #member { #field_names, .. } => Ok(#evo_member { #e_impl }),
402 }
403 } else {
404 quote! {
405 #member ( #field_letters ) => Ok(#evo_member ( #e_impl )),
406 }
407 };
408 let evo_impl = if is_empty {
409 quote! {
410 #member => #devo_member,
411 }
412 } else if is_named {
413 quote! {
414 #member { #field_names } => #devo_member { #d_impl },
415 }
416 } else {
417 quote! {
418 #member ( #field_letters, .. ) => #devo_member ( #d_impl ),
419 }
420 };
421
422 let span = ident.span();
423 if is_named && is_devo {
424 warn.push(
425 syn::Error::new(
426 span,
427 "#[devo] does nothing on enum variants with named fields",
428 )
429 .into_compile_error(),
430 );
431 }
432
433 if is_empty && is_devo {
434 warn.push(
435 syn::Error::new(span, "#[devo] does nothing on unit enum variants")
436 .into_compile_error(),
437 );
438 }
439
440 (is_untagged, tokens, warn, evo_impl, devo_impl)
441}
442
443fn render_tuple_field(
444 parent_ty: TokenStream,
445 Field { vis, attrs, ty, .. }: Field,
446 devo_attr: &Ident,
447 i: usize,
448 l: Option<&str>,
449 fallback_type: &Type,
450) -> (bool, TokenStream, TokenStream, TokenStream) {
451 let ty = &ty;
452 let (is_devo, _, attrs) = render_attrs(attrs, devo_attr);
453 let member = (if let Some(l) = l {
454 l.to_string()
455 } else {
456 format!("self.{}", i)
457 })
458 .parse::<TokenStream>()
459 .unwrap();
460 let idx = i.to_string().to_token_stream();
461 if is_devo {
462 if let Some(id) = match ty {
463 Type::Path(p) => p.path.get_ident(),
464 _ => None,
465 } {
466 let devolved_ident = format_ident!("{}", id);
467 return (
468 is_devo,
469 quote! {
470 #attrs
471 #vis <#devolved_ident as ::serde_devo::Devolve<#fallback_type>>::Devolved,
472 },
473 quote! {
474 <<#devolved_ident as ::serde_devo::Devolve<#fallback_type>>::Devolved as ::serde_devo::Evolve<#fallback_type>>::try_into_evolved(#member).map_err(|e| e.extend(#parent_ty, #idx))?,
475 },
476 quote! {
477 <#devolved_ident as ::serde_devo::Devolve<#fallback_type>>::into_devolved(#member),
478 },
479 );
480 }
481 }
482
483 (
484 is_devo,
485 quote! {
486 #attrs
487 #vis #ty,
488 },
489 quote! {
490 #member,
491 },
492 quote! {
493 #member,
494 },
495 )
496}
497
498fn render_field(
499 parent_ty: TokenStream,
500 Field {
501 vis,
502 attrs,
503 ident,
504 ty,
505 ..
506 }: Field,
507 devo_attr: &Ident,
508 is_enum: bool,
509 fallback_type: &Type,
510) -> (bool, TokenStream, TokenStream, TokenStream) {
511 let ty = &ty;
512 let (is_devo, _, attrs) = render_attrs(attrs, devo_attr);
513 let i = format!("{}", ident.as_ref().unwrap());
514 let member = (if is_enum {
515 i.clone()
516 } else {
517 format!("self.{}", ident.as_ref().unwrap())
518 })
519 .parse::<TokenStream>()
520 .unwrap();
521 if is_devo {
522 if let Some(id) = match ty {
523 Type::Path(p) => p.path.get_ident(),
524 _ => None,
525 } {
526 let devolved_ident = format_ident!("{}", id);
527 return (
528 is_devo,
529 quote! {
530 #attrs
531 #vis #ident: <#devolved_ident as ::serde_devo::Devolve<#fallback_type>>::Devolved,
532 },
533 quote! {
534 #ident: <<#devolved_ident as ::serde_devo::Devolve<#fallback_type>>::Devolved as ::serde_devo::Evolve<#fallback_type>>::try_into_evolved(#member).map_err(|e| e.extend(#parent_ty, #i))?,
535 },
536 quote! {
537 #ident: <#devolved_ident as ::serde_devo::Devolve<#fallback_type>>::into_devolved(#member),
538 },
539 );
540 }
541 }
542
543 (
544 is_devo,
545 quote! {
546 #attrs
547 #vis #ident: #ty,
548 },
549 quote! {
550 #ident: #member,
551 },
552 quote! {
553 #ident: #member,
554 },
555 )
556}
557
558fn render_attrs(
559 attrs: impl IntoIterator<Item = Attribute>,
560 devo_attr: &Ident,
561) -> (bool, bool, TokenStream) {
562 let (is_devo, is_untagged, tokens) = attrs.into_iter().fold(
563 (false, false, vec![]),
564 |(b, mut t, mut v), attr| match &attr.meta {
565 Meta::Path(name) if name.get_ident() == Some(devo_attr) => (true, t, v),
566 Meta::List(list) if list.path.get_ident() == Some(&format_ident!("serde")) => {
567 let _ = list.parse_nested_meta(|meta| {
568 if meta.path.is_ident("untagged") {
569 t = true
570 }
571 Ok(())
572 });
573 v.push(quote! {
574 #attr
575 });
576 (b, t, v)
577 }
578 _ => (b, t, v),
579 },
580 );
581
582 (is_devo, is_untagged, tokens.into_iter().collect())
583}