1#![allow(non_snake_case)]
3
4use proc_macro::{Delimiter, TokenStream, TokenTree, token_stream::IntoIter};
5use proc_macro2::{
6 Ident as Ident2, Punct as Punct2, Spacing as Spacing2, Span as Span2,
7 TokenStream as TokenStream2, TokenTree as TokenTree2,
8};
9use quote::quote;
10use regex::{Match, Regex};
11use std::collections::HashMap;
12use std::iter::Peekable;
13use std::ops::Range;
14use std::sync::LazyLock;
15use syn::{
16 AttrStyle, Attribute, Data, DataEnum, DataStruct, DeriveInput, Expr,
17 ExprLit, Field, FieldMutability, Fields, FieldsNamed, FieldsUnnamed, Lit,
18 LitInt, LitStr, MacroDelimiter, Meta, MetaList, Path, PathArguments,
19 PathSegment, Type, TypePath, Variant, Visibility, parse_macro_input,
20 token::{Brace, Bracket, Colon, Paren, Pound, Pub},
21};
22
23#[proc_macro_attribute]
27pub fn err_marks_the_spot(attr: TokenStream, item: TokenStream) -> TokenStream {
28 let item2 = item.clone();
29 let type_item @ DeriveInput {
30 attrs: item_attrs,
31 vis: item_vis,
32 ident: item_ident,
33 generics: item_generics,
34 data: item_data,
35 } = &parse_macro_input!(item2 as DeriveInput);
36
37 let type_attr_args = TypeAttrArgs::parse(attr);
38 let field_attrs = type_attr_args.field_attr_vec();
39 let ctor_attrs = type_attr_args.ctor_attr_vec();
40 let impl_ctors_for_type = generate_ctor_impl_block(
41 &ctor_attrs,
42 &item_ident,
43 &item_data,
44 &field_attrs,
45 );
46
47 let augmented_type_item = DeriveInput {
48 attrs: item_attrs.clone(),
49 vis: item_vis.clone(),
50 ident: item_ident.clone(),
51 generics: item_generics.clone(),
52 data: match &item_data {
53 Data::Union(_) => panic!("Unions are not supported"),
54 Data::Enum(e) => Data::Enum(augment_enum(
55 type_attr_args.build_feature.as_ref(),
56 &e,
57 &field_attrs,
58 )),
59 Data::Struct(s) => Data::Struct(augment_struct(
60 type_attr_args.build_feature.as_ref(),
61 &s,
62 &field_attrs,
63 )),
64 },
65 };
66
67 let impl_Display_for_type: TokenStream2 = gen_impl_Display_for_type(
68 type_attr_args.build_feature.as_ref(),
69 &type_item,
70 );
71
72 TokenStream::from(quote! {
73 #augmented_type_item
74 #impl_ctors_for_type
75 #impl_Display_for_type
76 })
77}
78
79#[rustfmt::skip]
80fn augment_enum(
81 build_feature: Option<&BuildFeatureAttr>,
82 e: &DataEnum,
83 field_attrs: &[Attribute],
84) -> DataEnum {
85 let output_variants = e.variants.iter()
86 .map(|Variant { attrs, ident, fields, discriminant }| {
87 if discriminant.is_some() {
88 panic!("Enum variant discriminants are not supported");
89 }
90 let field_name = Some(Ident2::new("ctx", Span2::call_site()));
91 let field_vis = Visibility::Inherited;
92 let output_fields = match fields {
93 Fields::Named(n) => Fields::Named(FieldsNamed {
94 brace_token: n.brace_token,
95 named: std::iter::empty()
96 .chain(n.named.iter().cloned())
97 .chain(vec![
98 ctx_field(
99 build_feature,
100 field_attrs,
101 field_vis,
102 field_name
103 )
104 ])
105 .collect(),
106 }),
107 Fields::Unit => Fields::Named(FieldsNamed {
108 brace_token: Brace(Span2::call_site()),
109 named: std::iter::empty()
110 .chain(vec![
111 ctx_field(
112 build_feature,
113 field_attrs,
114 field_vis,
115 field_name
116 )
117 ])
118 .collect(),
119 }),
120 Fields::Unnamed(u) => Fields::Unnamed(FieldsUnnamed {
121 paren_token: u.paren_token,
122 unnamed: std::iter::empty()
123 .chain(u.unnamed.iter().cloned()) .chain(vec![
125 ctx_field(
126 build_feature,
127 field_attrs,
128 field_vis,
129 None
130 )
131 ])
132 .collect(),
133 })
134 };
135 Variant {
136 attrs: attrs.clone(),
137 ident: ident.clone(),
138 fields: output_fields,
139 discriminant: discriminant.clone(),
140 }
141 })
142 .collect();
143 DataEnum {
144 enum_token: e.enum_token,
145 brace_token: e.brace_token,
146 variants: output_variants,
147 }
148}
149
150#[rustfmt::skip]
151fn augment_struct(
152 build_feature: Option<&BuildFeatureAttr>,
153 s: &DataStruct,
154 field_attrs: &[Attribute],
155) -> DataStruct {
156 let field_vis = Visibility::Public(Pub(Span2::call_site()));
157 let field_name = Some(Ident2::new("ctx", Span2::call_site()));
158 match &s.fields {
159 Fields::Named(n) => DataStruct {
160 struct_token: s.struct_token,
161 fields: Fields::Named(FieldsNamed {
162 brace_token: Brace(Span2::call_site()),
163 named: std::iter::empty()
164 .chain(n.named.iter().cloned()) .chain([
166 ctx_field(
167 build_feature,
168 field_attrs,
169 field_vis,
170 field_name
171 ),
172 ])
173 .collect(),
174 }),
175 semi_token: s.semi_token,
176 },
177 Fields::Unit => DataStruct {
178 struct_token: s.struct_token,
179 fields: Fields::Named(FieldsNamed {
180 brace_token: Brace(Span2::call_site()),
181 named: std::iter::empty()
182 .chain([
183 ctx_field(
184 build_feature,
185 field_attrs,
186 field_vis,
187 field_name
188 ),
189 ])
190 .collect(),
191 }),
192 semi_token: s.semi_token,
193 },
194 Fields::Unnamed(u) => DataStruct {
195 struct_token: s.struct_token,
196 fields: Fields::Unnamed(FieldsUnnamed {
197 paren_token: Paren(Span2::call_site()),
198 unnamed: std::iter::empty()
199 .chain(u.unnamed.iter().cloned()) .chain([
201 ctx_field(
202 build_feature,
203 field_attrs,
204 field_vis,
205 None
206 ),
207 ])
208 .collect(),
209 }),
210 semi_token: s.semi_token,
211 },
212 }
213}
214
215fn ctx_field(
217 build_feature: Option<&BuildFeatureAttr>,
218 field_attrs: &[Attribute],
219 vis: Visibility,
220 field_name: Option<Ident2>,
221) -> Field {
222 Field {
223 attrs: field_attrs.to_vec().into_iter()
224 .chain(if let Some(feature) = build_feature {
225 vec![ feature.to_attr() ]
226 } else {
227 vec![]
228 })
229 .collect(),
230 vis,
231 mutability: FieldMutability::None,
232 ident: field_name,
233 colon_token: Some(Colon(Span2::call_site())),
234 ty: Type::Path(TypePath {
235 qself: None,
236 path: Path {
237 leading_colon: None,
238 segments: vec![
239 PathSegment {
240 ident: Ident2::new("err_marks_the_spot", Span2::call_site()),
241 arguments: PathArguments::None,
242 },
243 PathSegment {
244 ident: Ident2::new("ErrorCtx", Span2::call_site()),
245 arguments: PathArguments::None,
246 },
247 ].into_iter().collect()
248 },
249 }),
250 }
251}
252
253fn generate_ctor_impl_block(
254 ctor_attrs: &[Attribute],
255 type_name: &Ident2,
256 item_data: &Data,
257 field_attrs: &[Attribute],
258) -> TokenStream2 {
259 match &item_data {
260 Data::Union(_) => panic!("Unions are not supported"),
261 Data::Enum(e) => {
262 let enum_ctors = generate_enum_ctors(&e, field_attrs, ctor_attrs);
263 quote! {
264 impl #type_name {
265 #( #enum_ctors )*
266 }
267 }
268 },
269 Data::Struct(s) => {
270 let struct_ctor = generate_struct_ctor(&s, field_attrs, ctor_attrs);
271 quote! {
272 impl #type_name {
273 #struct_ctor
274 }
275 }
276 },
277 }
278}
279
280fn generate_struct_ctor(
281 s: &DataStruct,
282 field_attrs: &[Attribute],
283 ctor_attrs: &[Attribute],
284) -> TokenStream2 {
285 match &s.fields {
286 Fields::Named(n) => {
287 let params = n.named.iter()
288 .map(|Field { ident, ty, .. }| quote! { #ident : impl Into<#ty> });
289 let field_initializers = n.named.iter()
290 .map(|Field { ident, ty: _, .. }| quote! { #ident: #ident.into() })
291 .chain([
292 quote! {
293 #(#field_attrs)*
294 ctx: err_marks_the_spot::ErrorCtx::new(),
295 },
296 ]);
297 quote! {
298 #(#ctor_attrs)*
299 #[track_caller]
300 pub fn new( #(#params),* ) -> Self {
301 Self {
302 #(#field_initializers),*
303 }
304 }
305 }
306 },
307 Fields::Unit => {
308 let field_initializers = std::iter::empty()
309 .chain([
310 quote! {
311 #(#field_attrs)*
312 ctx: err_marks_the_spot::ErrorCtx::new(),
313 },
314 ]);
315 quote! {
316 #(#ctor_attrs)*
317 #[track_caller]
318 pub fn new() -> Self {
319 Self {
320 #(#field_initializers),*
321 }
322 }
323 }
324 },
325 Fields::Unnamed(u) => {
326 let params = u.unnamed.iter().enumerate()
327 .map(|(i, Field { ident: _, ty, .. })| {
328 let ident = format!("field{i}");
329 let ident = Ident2::new(&ident, Span2::call_site());
330 quote! { #ident : impl Into<#ty> }
331 });
332 let field_initializers = u.unnamed.iter().enumerate()
333 .map(|(i, Field { ident: _, ty: _, .. })| {
334 let ident = format!("field{i}");
335 let ident = Ident2::new(&ident, Span2::call_site());
336 quote! { #ident.into() }
337 })
338 .chain([
339 quote! {
340 #(#field_attrs)*
341 err_marks_the_spot::ErrorCtx::new(),
342 },
343 ]);
344 quote! {
345 #(#ctor_attrs)*
346 #[track_caller]
347 pub fn new( #(#params),* ) -> Self {
348 Self(
349 #(#field_initializers),*
350 )
351 }
352 }
353 }
354 }
355}
356
357fn generate_enum_ctors(
358 e: &DataEnum,
359 field_attrs: &[Attribute],
360 ctor_attrs: &[Attribute],
361) -> Vec<TokenStream2> {
362 e.variants.iter()
363 .map(|Variant { ident, fields, .. }| {
364 let variant_name = ident;
365 let ctor_name = format!("new_{ident}");
366 let ctor_name = Ident2::new(&ctor_name, Span2::call_site());
367 match fields {
368 Fields::Named(n) => {
369 let params = n.named.iter()
370 .map(|Field { ident, ty, .. }| {
371 quote! { #ident : impl Into<#ty> }
372 });
373 let field_initializers = n.named.iter()
374 .map(|Field { ident, ty: _, .. }| {
375 quote! { #ident: #ident.into() }
376 })
377 .chain([
378 quote! {
379 #(#field_attrs)*
380 ctx: err_marks_the_spot::ErrorCtx::new(),
381 },
382 ]);
383 quote! {
384 #(#ctor_attrs)*
385 #[track_caller]
386 pub fn #ctor_name( #(#params),* ) -> Self {
387 Self::#variant_name {
388 #(#field_initializers),*
389 }
390 }
391 }
392 },
393 Fields::Unit => {
394 let field_initializers = std::iter::empty()
395 .chain([
396 quote! {
397 #(#field_attrs)*
398 ctx: err_marks_the_spot::ErrorCtx::new(),
399 },
400 ]);
401 quote! {
402 #(#ctor_attrs)*
403 #[track_caller]
404 pub fn #ctor_name() -> Self {
405 Self::#variant_name {
406 #(#field_initializers),*
407 }
408 }
409 }
410 },
411 Fields::Unnamed(u) => {
412 let params = u.unnamed.iter().enumerate()
413 .map(|(i, Field { ident: _, ty, .. })| {
414 let ident = format!("field{i}");
415 let ident = Ident2::new(&ident, Span2::call_site());
416 quote! { #ident : impl Into<#ty> }
417 });
418 let field_initializers = u.unnamed.iter().enumerate()
419 .map(|(i, Field { ident: _, ty: _, .. })| {
420 let ident = format!("field{i}");
421 let ident = Ident2::new(&ident, Span2::call_site());
422 quote! { #ident.into() }
423 })
424 .chain([
425 quote! {
426 #(#field_attrs)*
427 err_marks_the_spot::ErrorCtx::new()
428 },
429 ]);
430 quote! {
431 #(#ctor_attrs)*
432 #[track_caller]
433 pub fn #ctor_name( #(#params),* ) -> Self {
434 Self::#variant_name(
435 #(#field_initializers),*
436 )
437 }
438 }
439 },
440 }
441 })
442 .collect()
443}
444
445#[derive(Debug)]
446struct TypeAttrArgs {
447 build_feature: Option<BuildFeatureAttr>,
448 inline_ctors: Option<InlineCtorsAttr>,
449}
450
451impl TypeAttrArgs {
452 fn parse(attr: TokenStream) -> Self {
453 let mut attr_iter = attr.into_iter().peekable();
454 let mut field_attrs = Self {
455 build_feature: None,
456 inline_ctors: None,
457 };
458 let mut loop_count = 0;
459 while let Some(tt) = attr_iter.peek() {
460 let peeked = tt.to_string();
461 match &*peeked {
466 "," if loop_count > 0 => {
467 attr_arg::parse_comma_token(&mut attr_iter);
468 }
469 "feature" => {
470 let arg = BuildFeatureAttr::parse(&mut attr_iter);
471 field_attrs.build_feature = Some(arg);
472 },
473 "inline_ctors" => {
474 let arg = InlineCtorsAttr::parse(&mut attr_iter);
475 field_attrs.inline_ctors = Some(arg);
476 },
477 _ => panic!("Expected attr name 'feature', 'inline_ctors', got {peeked}")
478 }
479 loop_count += 1;
480 }
481 field_attrs
482 }
483
484 fn field_attr_vec(&self) -> Vec<Attribute> {
485 let mut vec = vec![];
486 if let Some(build_flag) = &self.build_feature {
487 vec.push(build_flag.to_attr());
488 }
489 vec
490 }
491
492 fn ctor_attr_vec(&self) -> Vec<Attribute> {
493 let mut vec = vec![];
494 if let Some(inline_ctors) = &self.inline_ctors {
495 vec.push(inline_ctors.to_attr());
496 }
497 vec
498 }
499}
500
501#[allow(unused)]
504#[derive(Debug)]
505struct BuildFeatureAttr {
506 name: Ident2,
507 name_stream: TokenStream2,
508 value: Expr,
509}
510
511impl BuildFeatureAttr {
512 fn parse(attr_iter: &mut Peekable<IntoIter>) -> Self {
513 let attr_arg_name = "feature";
514 let (name, name_stream) = attr_arg::parse_name(attr_iter, attr_arg_name);
515 attr_arg::parse_eq_token(attr_iter, &attr_arg_name);
516 let value: Expr = attr_arg::parse_value_expr(attr_iter, attr_arg_name);
517 Self { name, name_stream, value }
518 }
519
520 fn to_attr(&self) -> Attribute {
521 Attribute {
522 pound_token: Pound(Span2::call_site()),
523 style: AttrStyle::Outer,
524 bracket_token: Bracket(Span2::call_site()),
525 meta: Meta::List(MetaList {
526 path: Path {
527 leading_colon: None,
528 segments: [
529 PathSegment {
530 ident: Ident2::new("cfg", Span2::call_site()),
531 arguments: PathArguments::None,
532 },
533 ].into_iter().collect(),
534 },
535 delimiter: MacroDelimiter::Paren(Paren(Span2::call_site())),
536 tokens: {
537 let mut stream = TokenStream2::new();
538 stream.extend({
539 let feature = Ident2::new("feature", Span2::call_site());
540 let feature = TokenTree2::Ident(feature);
541 TokenStream2::from(feature.clone())
542 });
543 stream.extend({
544 let eq = Punct2::new('=', Spacing2::Alone);
545 let eq = TokenTree2::Punct(eq);
546 TokenStream2::from(eq)
547 });
548 let feature = &self.value;
549 stream.extend(quote! {
550 #feature
551 });
552 stream
553 },
554 }),
555 }
556 }
557}
558
559#[derive(Debug)]
562struct InlineCtorsAttr {
563 #[allow(unused)]
564 name: Ident2,
565 value: Option<Ident2>,
566}
567
568impl InlineCtorsAttr {
569 fn parse(attr_iter: &mut Peekable<IntoIter>) -> Self {
570 let attr_arg_name = "inline_ctors";
571 let (name, _stream) = attr_arg::parse_name(attr_iter, attr_arg_name);
572 let value = attr_arg::parse_parenthesized_value_ident(attr_iter);
573 Self { name, value }
574 }
575
576 fn to_attr(&self) -> Attribute {
577 Attribute {
578 pound_token: Pound(Span2::call_site()),
579 style: AttrStyle::Outer,
580 bracket_token: Bracket(Span2::call_site()),
581 meta: if let Some(value) = self.value.as_ref() {
582 Meta::List(MetaList {
583 path: Path {
584 leading_colon: None,
585 segments: [
586 PathSegment {
587 ident: Ident2::new("inline", Span2::call_site()),
588 arguments: PathArguments::None,
589 },
590 ].into_iter().collect(),
591 },
592 delimiter: MacroDelimiter::Paren(Paren(Span2::call_site())),
593 tokens: TokenStream2::from(TokenTree2::Ident(value.clone())),
594 })
595 } else {
596 Meta::Path(Path {
597 leading_colon: None,
598 segments: [
599 PathSegment {
600 ident: Ident2::new("inline", Span2::call_site()),
601 arguments: PathArguments::None,
602 },
603 ].into_iter().collect(),
604 })
605 },
606 }
607 }
608}
609
610mod attr_arg {
611 use super::*;
612
613 pub fn parse_name(
614 attr_iter: &mut Peekable<IntoIter>,
615 attr_arg_name: &str,
616 ) -> (Ident2, TokenStream2) {
617 let attr_name_tt: TokenTree = attr_iter.next().unwrap();
618 assert_eq!(attr_name_tt.to_string(), attr_arg_name);
619 let name_stream: TokenStream2 = TokenStream::from(attr_name_tt).into();
620 let name: Ident2 = syn::parse2(name_stream.clone()).unwrap_or_else(|_| {
621 panic!("Failed to parse attribute argument: {attr_arg_name}")
622 });
623 (name, name_stream)
624 }
625
626 pub fn parse_eq_token(
627 attr_iter: &mut Peekable<IntoIter>,
628 attr_arg_name: &str,
629 ) {
630 let _eq_tt = attr_iter.next();
631 let _eq = match _eq_tt {
632 Some(TokenTree::Punct(p)) => {
633 assert_eq!(p.as_char(), '=', "{attr_arg_name}");
634 },
635 Some(tt) => panic!("Unrecognized token tree: {tt}"),
636 None => panic!("Expected '='"),
637 };
638 }
639
640 pub fn parse_value_expr(
641 attr_iter: &mut Peekable<IntoIter>,
642 attr_arg_name: &str,
643 ) -> Expr {
644 match attr_iter.next() {
645 Some(tt @ TokenTree::Literal(_)) => {
646 let stream: TokenStream2 = TokenStream::from(tt).into();
647 syn::parse2::<Expr>(stream).unwrap_or_else(|_| panic!(
648 "Failed to parse value expr of attribute argument {}",
649 attr_arg_name
650 ))
651 }
652 Some(tt) => panic!(
653 "Expected value expr of attribute argument {}, got token tree {}",
654 attr_arg_name, tt
655 ),
656 None => panic!(
657 "Expected value expr of attribute argument {}",
658 attr_arg_name,
659 ),
660 }
661 }
662
663 pub fn parse_parenthesized_value_ident(
664 attr_iter: &mut Peekable<IntoIter>,
665 ) -> Option<Ident2> {
666 match attr_iter.next() {
667 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => {
668 let inner_stream = TokenStream2::from(g.stream());
669 let attr_arg_value = syn::parse2::<Ident2>(inner_stream)
670 .expect("Expected a parenthesized value ident");
671 Some(attr_arg_value)
672 },
673 _ => None,
674 }
675 }
676
677 pub fn parse_comma_token(attr_iter: &mut Peekable<IntoIter>) {
678 let _comma_tt = attr_iter.next();
679 let _comma = match _comma_tt {
680 Some(TokenTree::Punct(p)) => assert_eq!(p.as_char(), ','),
681 Some(tt) => panic!("Unrecognized token tree: {tt}"),
682 None => panic!("Expected ','"),
683 };
684 }
685}
686
687fn gen_impl_Display_for_type(
688 build_feature: Option<&BuildFeatureAttr>,
689 type_item: &DeriveInput,
690) -> TokenStream2 {
691 let type_item_name = &type_item.ident;
692 let type_item_docstrs: Vec<String> = get_docstrs_from_attrs(&type_item.attrs);
693 let item_field_map: FieldMap = match &type_item.data {
694 Data::Union(_) => panic!("Unions are not supported"),
695 Data::Enum(e) => {
696 let enum_fields_map = e.variants
697 .iter()
698 .map(|Variant { ident, fields, .. }| (
699 ident.clone(),
700 create_fields_map(&fields)
701 ))
702 .collect();
703 FieldMap::Enum(enum_fields_map)
704 }
705 Data::Struct(s) => FieldMap::Struct(create_fields_map(&s.fields)),
706 };
707 let struct_impl_Display_contents = get_struct_impl_Display_contents(
708 build_feature,
709 type_item,
710 &type_item_docstrs,
711 &item_field_map,
712 );
713 let enum_impl_Display_contents = get_enum_impl_Display_contents(
714 build_feature,
715 type_item,
716 &item_field_map,
717 );
718 let impl_Display_contents = if let FieldMap::Struct(_) = item_field_map {
719 quote! { #struct_impl_Display_contents }
720 } else {
721 quote! { #enum_impl_Display_contents }
722 };
723 quote! {
724 impl std::fmt::Display for #type_item_name {
725 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
726 #impl_Display_contents
727 Ok(())
728 }
729 }
730 }
731}
732
733fn get_struct_impl_Display_contents(
734 build_feature: Option<&BuildFeatureAttr>,
735 type_item: &DeriveInput,
736 type_item_docstrs: &[String],
737 item_field_map: &FieldMap,
738) -> TokenStream2 {
739 let type_item_name = &type_item.ident;
740 let quotes: Vec<TokenStream2> = type_item_docstrs.iter()
741 .filter(|_| matches!(item_field_map, FieldMap::Struct(_)))
742 .map(|item_docstr| {
743 let item_docstr_fields = find_docstring_fields(item_docstr);
744 let modified_item_docstr = modify_docstr(
745 &item_docstr,
746 &item_docstr_fields
747 );
748 let trimmed_item_docstr = LitStr::new(
749 &modified_item_docstr,
750 Span2::call_site()
751 );
752 assert!(matches!(type_item.data, Data::Struct(_)));
753 let FieldMap::Struct(field_map) = &item_field_map else { unreachable!() };
754 let fields: Vec<&FieldIdToken> = item_docstr_fields.iter()
755 .map(|(_pos, field_name)| {
756 field_map.get(*field_name).unwrap_or_else(|| panic!(
757 "Type {} no has no field '{}'",
758 type_item_name, field_name
759 ))
760 })
761 .collect();
762 quote! {
763 writeln!(
764 f,
765 #trimmed_item_docstr,
766 #(&self . #fields),*
767 )?;
768 }
769 })
770 .chain([
771 if let Some(BuildFeatureAttr { name, value, .. }) = build_feature {
772 assert_eq!(name, &Ident2::new("feature", Span2::call_site()));
773 quote! {
777 #[cfg(feature = #value)]
778 writeln!(f, "")?;
779 }
780 } else {
781 quote! { writeln!(f, "")?; }
783 },
784 ])
785 .chain(if let Data::Struct(s) = &type_item.data {
786 vec![writeln_for_ErrorCtx_field(build_feature, &s.fields)]
788 } else {
789 vec![]
790 })
791 .collect();
792 quote! { #(#quotes)* }
793}
794
795fn get_enum_impl_Display_contents(
796 build_feature: Option<&BuildFeatureAttr>,
797 type_item: &DeriveInput,
798 item_field_map: &FieldMap,
799) -> TokenStream2 {
800 let FieldMap::Enum(field_map) = &item_field_map else { return quote!{} };
801 let Data::Enum(data) = &type_item.data else { return quote!{} };
802 let DataEnum { variants, .. } = data;
803
804 let variant_writelns: Vec<_> = variants.iter()
805 .map(|Variant { attrs, ident: variant_name, fields, .. }| {
806 let vdocstrs = get_docstrs_from_attrs(attrs);
807
808 let vbindings: Vec<TokenStream2> = match fields {
809 Fields::Named(n) => n.named.iter()
810 .map(|field| field.ident.clone().unwrap())
811 .map(|field_name| quote! { #field_name })
812 .collect(),
813 Fields::Unit => vec![],
814 Fields::Unnamed(u) => u.unnamed.iter()
815 .enumerate()
816 .map(|(i, field)| {
817 field.ident.clone().unwrap_or_else(|| {
818 Ident2::new(
819 &format!("f{i}"),
820 Span2::call_site()
821 )
822 })
823 })
824 .map(|field_name| quote! { #field_name })
825 .collect(),
826 };
827
828 let vbind_list = match fields {
829 Fields::Named(_) => quote! { { #(#vbindings ,)* } },
830 Fields::Unit => quote! { { #(#vbindings ,)* } },
831 Fields::Unnamed(_) => quote! { ( #(#vbindings ,)* ) },
832 };
833 let vbind_list_with_ctx = match fields {
834 Fields::Named(_) => quote! { { #(#vbindings ,)* ctx, } },
835 Fields::Unit => quote! { { #(#vbindings ,)* ctx, } },
836 Fields::Unnamed(_) => quote! { ( #(#vbindings ,)* ctx, ) },
837 };
838
839 let vdocstr_writelns: Vec<TokenStream2> = vdocstrs.iter()
840 .map(|variant_docstr| {
841 let variant_docstr_fields = find_docstring_fields(
842 variant_docstr
843 );
844 let modified_variant_docstr = modify_docstr(
845 &variant_docstr,
846 &variant_docstr_fields
847 );
848 let trimmed_variant_docstr = LitStr::new(
849 &modified_variant_docstr,
850 Span2::call_site()
851 );
852 let variant_docstr_fields: Vec<Ident2> =
853 variant_docstr_fields
854 .iter()
855 .map(|(_pos, field_name)| {
856 let enum_field_map = field_map.get(variant_name)
857 .unwrap_or_else(|| panic!(
858 "Type {} no has no variant '{}'",
859 type_item.ident, variant_name
860 ));
861 let field_token = enum_field_map
862 .get(*field_name)
863 .unwrap_or_else(|| panic!(
864 "Type variant {}::{} no has no field '{}'",
865 type_item.ident, variant_name, field_name
866 ));
867 match field_token {
868 FieldIdToken::Ident(ident) => ident.clone(),
869 FieldIdToken::Literal(lit) => Ident2::new(
870 &format!("f{lit}"),
871 Span2::call_site()
872 ),
873 }
874 })
875 .collect();
876 quote! {
877 writeln!(
878 f,
879 #trimmed_variant_docstr,
880 #(& #variant_docstr_fields),*
881 )?;
882 }
883 })
884 .chain([
885 if let Some(bf) = build_feature {
890 assert_eq!(
891 bf.name,
892 Ident2::new("feature", Span2::call_site())
893 );
894 let feature = &bf.value;
895 quote! {
896 #[cfg(feature = #feature)]
897 writeln!(f, "")?;
898 #[cfg(feature = #feature)]
899 writeln!(f, "{}", &ctx)?;
900 }
901 } else {
902 quote! {
903 writeln!(f, "")?;
904 writeln!(f, "{}", &ctx)?;
905 }
906 }
907 ])
908 .collect();
909
910 if let Some(bf) = build_feature {
911 let feature = &bf.value;
912 quote! {
913 #[cfg(feature = #feature)]
914 Self :: #variant_name #vbind_list_with_ctx => {
915 #( #vdocstr_writelns )*
916 },
917
918 #[cfg(not(feature = #feature))]
919 Self :: #variant_name #vbind_list => {
920 #( #vdocstr_writelns )*
921 },
922 }
923 } else {
924 quote! {
925 Self :: #variant_name #vbind_list_with_ctx => {
926 #( #vdocstr_writelns )*
927 },
928 }
929 }
930
931 })
932 .collect();
933
934 quote! {
935 match self {
936 #( #variant_writelns )*
937 }
938 }
939}
940
941fn get_docstrs_from_attrs(attrs: &[Attribute]) -> Vec<String> {
942 attrs.iter()
943 .filter(|Attribute { meta, .. }| {
944 let Meta::NameValue(v) = meta else { return false };
945 let segs = v.path.segments.iter().collect::<Vec<_>>();
946 let [PathSegment { ident, arguments: PathArguments::None }] = &*segs
947 else { return false };
948 ident.to_string() == "doc"
949 })
950 .map(|Attribute { meta, .. }| {
951 let Meta::NameValue(v) = meta else { unreachable!() };
952 let Expr::Lit(ExprLit { lit, .. }) = &v.value else { unreachable!() };
953 let Lit::Str(s) = lit else { unreachable!() };
954 s.token().to_string()
955 .trim_start_matches(r#"" "#)
956 .trim_end_matches(r#"""#)
957 .to_string()
958 })
959 .collect()
960}
961
962fn modify_docstr(
965 docstr: &str,
966 fields_sites: &[(Range<usize>, &str)],
967) -> String {
968 fields_sites.iter()
969 .rev()
970 .fold(docstr.to_string(), |docstr, &(Range { start, end }, _)| {
971 const EMPTY_FMT_STR: &str = "{}";
972 let preamble = &docstr[..start];
973 let postamble = &docstr[end..];
974 preamble.to_string() + EMPTY_FMT_STR + postamble
975 })
976}
977
978fn find_docstring_fields(docstr: &str) -> Vec<(Range<usize>, &str)> {
981 static FIELD_SPECIFIER_REGEX: LazyLock<Regex> = LazyLock::new(|| {
982 Regex::new(r"\{[A-Za-z0-9_]+\}").unwrap()
983 });
984 FIELD_SPECIFIER_REGEX.find_iter(docstr)
985 .map(|m: Match| {
986 let field_name = m.as_str()
987 .strip_prefix('{').unwrap()
988 .strip_suffix('}').unwrap();
989 (m.range(), field_name)
990 })
991 .collect()
992}
993
994fn writeln_for_ErrorCtx_field(
995 build_feature: Option<&BuildFeatureAttr>,
996 fields: &Fields,
997) -> TokenStream2 {
998 match &fields {
999 Fields::Named(_) | Fields::Unit => {
1000 let ident = Ident2::new("ctx", Span2::call_site());
1001 let err_ctx_field = FieldIdToken::Ident(ident);
1002 if let Some(BuildFeatureAttr { name, value, .. }) = build_feature {
1003 assert_eq!(name, &Ident2::new("feature", Span2::call_site()));
1004 quote! {
1007 #[cfg(feature = #value)]
1008 writeln!(f, "{}", &self . #err_ctx_field)?;
1009 }
1010 } else {
1011 quote! {
1013 writeln!(f, "{}", &self . #err_ctx_field)?;
1014 }
1015 }
1016 }
1017 Fields::Unnamed(u) => {
1018 let field_num = u.unnamed.iter()
1019 .enumerate()
1020 .last()
1021 .map(|(field_num, _field)| field_num)
1022 .unwrap_or_default();
1023 let lit = format!("{}", field_num + 1);
1024 let lit = LitInt::new(&lit, Span2::call_site());
1025 let err_ctx_field = FieldIdToken::Literal(lit);
1026 if let Some(BuildFeatureAttr { name, value, .. }) = build_feature {
1027 assert_eq!(name, &Ident2::new("feature", Span2::call_site()));
1028 quote! {
1031 #[cfg(feature = #value)]
1032 writeln!(f, "{}", &self . #err_ctx_field)?;
1033 }
1034 } else {
1035 quote! {
1037 writeln!(f, "{}", &self . #err_ctx_field)?;
1038 }
1039 }
1040 }
1041 }
1042}
1043
1044#[derive(Debug)]
1048enum FieldIdToken {
1049 Ident(Ident2),
1050 Literal(LitInt),
1051}
1052
1053impl quote::ToTokens for FieldIdToken {
1054 fn to_tokens(&self, tokens: &mut TokenStream2) {
1055 let ts = match self {
1056 Self::Ident(ident) => quote! { #ident },
1057 Self::Literal(literal) => quote! { #literal },
1058 };
1059 tokens.extend(ts);
1060 }
1061}
1062
1063enum FieldMap {
1064 Struct(HashMap<String, FieldIdToken>),
1065 Enum(HashMap<Ident2, HashMap<String, FieldIdToken>>), }
1067
1068fn single_field_mapping(
1069 ident: Option<Ident2>,
1070 num: Option<usize>,
1071) -> (String, FieldIdToken) {
1072 match (ident, num) {
1073 (Some(ident), _) => {
1074 let field_name = format!("{ident}");
1075 (field_name, FieldIdToken::Ident(ident.clone()))
1076 }
1077 (_, Some(num)) => {
1078 let field_num = format!("{num}");
1079 let lit = LitInt::new(&field_num, Span2::call_site());
1080 (field_num, FieldIdToken::Literal(lit))
1081 },
1082 _ => unreachable!(),
1083 }
1084}
1085
1086fn create_fields_map(fields: &Fields) -> HashMap<String, FieldIdToken> {
1087 match fields {
1088 Fields::Named(n) => n.named.iter()
1089 .map(|Field { ident, .. }| {
1090 single_field_mapping(ident.clone(), None)
1091 })
1092 .chain({ let ident = Ident2::new("ctx", Span2::call_site());
1094 [ single_field_mapping(Some(ident), None) ]
1095 })
1096 .collect(),
1097 Fields::Unit => std::iter::empty()
1098 .chain({ let ident = Ident2::new("ctx", Span2::call_site());
1100 [ single_field_mapping(Some(ident), None) ]
1101 })
1102 .collect(),
1103 Fields::Unnamed(u) => {
1104 let mut fields: Vec<(String, FieldIdToken)> = u.unnamed.iter()
1105 .enumerate()
1106 .map(|(i, Field { ident, .. })| {
1107 assert!(ident.is_none());
1108 single_field_mapping(None, Some(i))
1109 })
1110 .collect();
1111 fields.push({ single_field_mapping(None, Some(fields.len()))
1113 });
1114 fields.into_iter().collect()
1115 },
1116 }
1117}