ezno_parser_visitable_derive/
macro.rs1use proc_macro::TokenStream;
2use std::error::Error;
3use string_cases::StringCasesExt;
4use syn_helpers::{
5 derive_trait,
6 proc_macro2::{Ident, Span},
7 quote,
8 syn::{
9 self, parse_macro_input, parse_quote, DeriveInput, Stmt, __private::quote::format_ident,
10 parse::Parse,
11 },
12 Constructable, FieldMut, HasAttributes, NamedOrUnnamedFieldMut, Trait, TraitItem,
13};
14
15const VISIT_SELF_NAME: &str = "visit_self";
17const VISIT_SKIP_NAME: &str = "visit_skip_field";
19const VISIT_WITH_CHAIN_NAME: &str = "visit_with_chain";
21
22#[proc_macro_derive(Visitable, attributes(visit_self, visit_skip_field, visit_custom_visit))]
24pub fn generate_visit_implementation(input: TokenStream) -> TokenStream {
25 let input = parse_macro_input!(input as DeriveInput);
26
27 let visit_item = TraitItem::new_method(
28 Ident::new("visit", Span::call_site()),
29 Some(vec![parse_quote!(TData)]),
30 syn_helpers::TypeOfSelf::Reference,
31 vec![
32 parse_quote!(visitors: &mut (impl crate::visiting::VisitorReceiver<TData> + ?Sized)),
33 parse_quote!(data: &mut TData),
34 parse_quote!(options: &crate::VisitOptions),
35 parse_quote!(chain: &mut ::temporary_annex::Annex<crate::visiting::Chain>),
36 ],
37 None,
38 |item| generated_visit_item(item, VisitType::Immutable),
39 );
40
41 let visit_mut_item = TraitItem::new_method(
42 Ident::new("visit_mut", Span::call_site()),
43 Some(vec![parse_quote!(TData)]),
44 syn_helpers::TypeOfSelf::MutableReference,
45 vec![
46 parse_quote!(visitors: &mut (impl crate::visiting::VisitorMutReceiver<TData> + ?Sized)),
47 parse_quote!(data: &mut TData),
48 parse_quote!(options: &crate::VisitOptions),
49 parse_quote!(chain: &mut ::temporary_annex::Annex<crate::visiting::Chain>),
50 ],
51 None,
52 |item| generated_visit_item(item, VisitType::Mutable),
53 );
54
55 let visitable_trait = Trait {
56 name: parse_quote!(crate::visiting::Visitable),
57 generic_parameters: None,
58 items: vec![visit_item, visit_mut_item],
59 };
60
61 let output = derive_trait(input, visitable_trait);
62
63 output.into()
64}
65
66#[derive(Clone, Copy)]
67enum VisitType {
68 Immutable,
69 Mutable,
70}
71
72fn generated_visit_item(
73 mut item: syn_helpers::Item,
74 visit_type: VisitType,
75) -> Result<Vec<Stmt>, Box<dyn Error>> {
76 let attributes = item.structure.get_attributes();
77
78 let visit_self = attributes.iter().find_map(|attr| {
79 attr.path().is_ident(VISIT_SELF_NAME).then_some({
80 let mut ident = None::<Ident>;
81 let res = attr.parse_nested_meta(|meta| {
82 if meta.path.is_ident("under") {
83 let value = meta.value()?;
84 ident = value.parse()?;
85 Ok(())
86 } else {
87 Err(meta.error("expected `under=...`"))
88 }
89 });
90 if res.is_ok() {
91 Some(ident.unwrap())
92 } else {
93 None
95 }
96 })
97 });
98
99 let visit_with_chain: Option<syn::Expr> = attributes
100 .iter()
101 .find_map(|attr| {
102 attr.path().is_ident(VISIT_WITH_CHAIN_NAME).then_some(attr.parse_args().ok())
103 })
104 .flatten();
105
106 let mut lines = Vec::new();
107
108 if let Some(expr_tokens) = visit_with_chain {
109 lines.push(parse_quote!( let mut chain = &mut chain.push_annex(#expr_tokens); ))
110 }
111
112 if let Some(under) = visit_self {
113 let mut_postfix =
114 matches!(visit_type, VisitType::Mutable).then_some("_mut").unwrap_or_default();
115
116 if let Some(under) = under {
117 let func_name = format_ident!("visit_{}{}", under, mut_postfix);
118 lines.push(parse_quote!(visitors.#func_name(self.into(), data, chain); ))
119 } else {
120 let struct_name_as_snake_case = &item.structure.get_name().to_string().to_snake_case();
121 let func_name = format_ident!("visit_{}{}", struct_name_as_snake_case, mut_postfix);
122 lines.push(parse_quote!( visitors.#func_name(self, data, chain); ))
123 }
124 }
125
126 let mut field_lines = item.map_constructable(|mut constructable| {
127 Ok(constructable
128 .get_fields_mut()
129 .fields_iterator_mut()
130 .flat_map(|mut field: NamedOrUnnamedFieldMut| -> Option<Stmt> {
131 let attributes = field.get_attributes();
132
133 let skip_field_attr =
134 attributes.iter().find(|attr| attr.path().is_ident(VISIT_SKIP_NAME));
135
136 let visit_with_chain = attributes.iter().find_map(|attr| {
142 attr.path()
144 .is_ident(VISIT_WITH_CHAIN_NAME)
145 .then_some(attr.parse_args_with(syn::Expr::parse).ok())
146 .flatten()
147 });
148
149 let chain = if let Some(expr_tokens) = visit_with_chain {
150 quote!(&mut chain.push_annex(#expr_tokens))
151 } else {
152 quote!(chain)
153 };
154
155 if skip_field_attr.is_none() {
156 let reference = field.get_reference();
157 Some(match visit_type {
158 VisitType::Immutable => parse_quote! {
159 crate::Visitable::visit(#reference, visitors, data, options, #chain);
160 },
161 VisitType::Mutable => parse_quote! {
162 crate::Visitable::visit_mut(#reference, visitors, data, options, #chain);
163 },
164 })
165 } else {
166 None
167 }
168 })
169 .collect::<Vec<_>>())
170 })?;
171
172 lines.append(&mut field_lines);
173
174 Ok(lines)
175}