1use proc_macro::TokenStream;
8use quote::{format_ident, quote};
9use syn::{
10 parse_macro_input, Data, DeriveInput, Field, Fields, GenericArgument, PathArguments, Type,
11};
12
13#[proc_macro_derive(AstNode, attributes(ast))]
14pub fn derive_ast_node(input: TokenStream) -> TokenStream {
16 let input = parse_macro_input!(input as DeriveInput);
17 expand_ast_node(&input).into()
18}
19
20fn expand_ast_node(input: &DeriveInput) -> proc_macro2::TokenStream {
21 let name = &input.ident;
22 let immutable = match &input.data {
23 Data::Struct(data) => visit_fields(&data.fields, false),
24 Data::Enum(data) => {
25 let arms = data.variants.iter().map(|variant| {
26 let variant_name = &variant.ident;
27 let (pattern, body) =
28 visit_variant_fields(name, variant_name, &variant.fields, false);
29 quote!(#pattern => { #body })
30 });
31 quote!(match self { #(#arms),* })
32 }
33 Data::Union(_) => quote!(),
34 };
35 let mutable = match &input.data {
36 Data::Struct(data) => visit_fields(&data.fields, true),
37 Data::Enum(data) => {
38 let arms = data.variants.iter().map(|variant| {
39 let variant_name = &variant.ident;
40 let (pattern, body) =
41 visit_variant_fields(name, variant_name, &variant.fields, true);
42 quote!(#pattern => { #body })
43 });
44 quote!(match self { #(#arms),* })
45 }
46 Data::Union(_) => quote!(),
47 };
48
49 quote! {
50 impl crate::ast_children::AstNode for #name {
51 fn visit_expressions<'ast, F>(
52 &'ast self,
53 path: &mut Vec<crate::ast_children::ChildPathSegment>,
54 visitor: &mut F,
55 )
56 where
57 F: FnMut(
58 &[crate::ast_children::ChildPathSegment],
59 &'ast crate::expressions::Expression,
60 ),
61 {
62 #immutable
63 }
64
65 fn visit_expressions_mut<F>(
66 &mut self,
67 visitor: &mut F,
68 )
69 where
70 F: FnMut(&mut crate::expressions::Expression),
71 {
72 #mutable
73 }
74 }
75 }
76}
77
78fn visit_fields(fields: &Fields, mutable: bool) -> proc_macro2::TokenStream {
79 match fields {
80 Fields::Named(fields) => {
81 let visits = fields.named.iter().filter_map(|field| {
82 if is_skipped(field) {
83 return None;
84 }
85 let ident = field.ident.as_ref().expect("named field");
86 let field_name = ident.to_string();
87 let access = quote!(&mut self.#ident);
88 let immutable_access = quote!(&self.#ident);
89 Some(if mutable {
90 mutable_visit(&field.ty, access)
91 } else {
92 let visit = immutable_visit(&field.ty, immutable_access);
93 quote! {
94 path.push(crate::ast_children::ChildPathSegment::Field(#field_name));
95 #visit
96 path.pop();
97 }
98 })
99 });
100 quote!(#(#visits)*)
101 }
102 Fields::Unnamed(fields) => {
103 let visits = fields
104 .unnamed
105 .iter()
106 .enumerate()
107 .filter_map(|(index, field)| {
108 if is_skipped(field) {
109 return None;
110 }
111 let index = syn::Index::from(index);
112 let access = quote!(&mut self.#index);
113 let immutable_access = quote!(&self.#index);
114 Some(if mutable {
115 mutable_visit(&field.ty, access)
116 } else {
117 let visit = immutable_visit(&field.ty, immutable_access);
118 quote! {
119 path.push(crate::ast_children::ChildPathSegment::Index(#index));
120 #visit
121 path.pop();
122 }
123 })
124 });
125 quote!(#(#visits)*)
126 }
127 Fields::Unit => quote!(),
128 }
129}
130
131fn visit_variant_fields(
132 enum_name: &syn::Ident,
133 variant_name: &syn::Ident,
134 fields: &Fields,
135 mutable: bool,
136) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
137 match fields {
138 Fields::Named(fields) => {
139 let bindings: Vec<_> = fields
140 .named
141 .iter()
142 .map(|field| {
143 let ident = field.ident.as_ref().expect("named field");
144 if is_skipped(field) || is_scalar(&field.ty) {
145 quote!(#ident: _)
146 } else {
147 quote!(#ident)
148 }
149 })
150 .collect();
151 let visits = fields.named.iter().filter_map(|field| {
152 if is_skipped(field) {
153 return None;
154 }
155 let ident = field.ident.as_ref().expect("named field");
156 let field_name = ident.to_string();
157 Some(if mutable {
158 mutable_visit(&field.ty, quote!(#ident))
159 } else {
160 let visit = immutable_visit(&field.ty, quote!(#ident));
161 quote! {
162 path.push(crate::ast_children::ChildPathSegment::Field(#field_name));
163 #visit
164 path.pop();
165 }
166 })
167 });
168 (
169 quote!(#enum_name::#variant_name { #(#bindings),* }),
170 quote!(#(#visits)*),
171 )
172 }
173 Fields::Unnamed(fields) => {
174 let bindings: Vec<_> = (0..fields.unnamed.len())
175 .map(|index| format_ident!("field_{index}"))
176 .collect();
177 let single_expression_payload = enum_name == "Expression" && fields.unnamed.len() == 1;
178 let visits = fields
179 .unnamed
180 .iter()
181 .zip(bindings.iter())
182 .enumerate()
183 .filter_map(|(index, (field, binding))| {
184 if is_skipped(field) {
185 return None;
186 }
187 Some(if mutable {
188 mutable_visit(&field.ty, quote!(#binding))
189 } else {
190 let visit = immutable_visit(&field.ty, quote!(#binding));
191 if single_expression_payload {
192 visit
193 } else {
194 quote! {
195 path.push(crate::ast_children::ChildPathSegment::Index(#index));
196 #visit
197 path.pop();
198 }
199 }
200 })
201 });
202 (
203 quote!(#enum_name::#variant_name(#(#bindings),*)),
204 quote!(#(#visits)*),
205 )
206 }
207 Fields::Unit => (quote!(#enum_name::#variant_name), quote!()),
208 }
209}
210
211fn immutable_visit(ty: &Type, access: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
212 if is_expression(ty) {
213 return quote!(visitor(path, #access););
214 }
215 if let Some(inner) = container_inner(ty, "Option") {
216 let visit = immutable_visit(inner, quote!(value));
217 return quote!(if let Some(value) = (#access).as_ref() { #visit });
218 }
219 if let Some(inner) = container_inner(ty, "Box") {
220 return immutable_visit(inner, quote!((#access).as_ref()));
221 }
222 if let Some(inner) = container_inner(ty, "Vec") {
223 let visit = immutable_visit(inner, quote!(value));
224 return quote! {
225 for (index, value) in (#access).iter().enumerate() {
226 path.push(crate::ast_children::ChildPathSegment::Index(index));
227 #visit
228 path.pop();
229 }
230 };
231 }
232 if let Type::Tuple(tuple) = ty {
233 let visits = tuple.elems.iter().enumerate().map(|(index, element)| {
234 let tuple_index = syn::Index::from(index);
235 let visit = immutable_visit(element, quote!(&(#access).#tuple_index));
236 quote! {
237 path.push(crate::ast_children::ChildPathSegment::Index(#index));
238 #visit
239 path.pop();
240 }
241 });
242 return quote!(#(#visits)*);
243 }
244 if is_scalar(ty) {
245 return quote!();
246 }
247 quote!(crate::ast_children::AstNode::visit_expressions(#access, path, visitor);)
248}
249
250fn mutable_visit(ty: &Type, access: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
251 if is_expression(ty) {
252 return quote!(visitor(#access););
253 }
254 if let Some(inner) = container_inner(ty, "Option") {
255 let visit = mutable_visit(inner, quote!(value));
256 return quote!(if let Some(value) = (#access).as_mut() { #visit });
257 }
258 if let Some(inner) = container_inner(ty, "Box") {
259 return mutable_visit(inner, quote!((#access).as_mut()));
260 }
261 if let Some(inner) = container_inner(ty, "Vec") {
262 let visit = mutable_visit(inner, quote!(value));
263 return quote!(for value in (#access).iter_mut() { #visit });
264 }
265 if let Type::Tuple(tuple) = ty {
266 let visits = tuple.elems.iter().enumerate().map(|(index, element)| {
267 let tuple_index = syn::Index::from(index);
268 mutable_visit(element, quote!(&mut (#access).#tuple_index))
269 });
270 return quote!(#(#visits)*);
271 }
272 if is_scalar(ty) {
273 return quote!();
274 }
275 quote!(crate::ast_children::AstNode::visit_expressions_mut(#access, visitor);)
276}
277
278fn container_inner<'a>(ty: &'a Type, expected: &str) -> Option<&'a Type> {
279 let Type::Path(path) = ty else { return None };
280 let segment = path.path.segments.last()?;
281 if segment.ident != expected {
282 return None;
283 }
284 let PathArguments::AngleBracketed(arguments) = &segment.arguments else {
285 return None;
286 };
287 arguments.args.iter().find_map(|argument| match argument {
288 GenericArgument::Type(ty) => Some(ty),
289 _ => None,
290 })
291}
292
293fn is_expression(ty: &Type) -> bool {
294 matches!(ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "Expression"))
295}
296
297fn is_scalar(ty: &Type) -> bool {
298 let Type::Path(path) = ty else { return false };
299 let Some(segment) = path.path.segments.last() else {
300 return false;
301 };
302 matches!(
303 segment.ident.to_string().as_str(),
304 "bool"
305 | "char"
306 | "str"
307 | "String"
308 | "usize"
309 | "isize"
310 | "u8"
311 | "u16"
312 | "u32"
313 | "u64"
314 | "u128"
315 | "i8"
316 | "i16"
317 | "i32"
318 | "i64"
319 | "i128"
320 | "f32"
321 | "f64"
322 | "Span"
323 )
324}
325
326fn is_skipped(field: &Field) -> bool {
327 field.attrs.iter().any(|attribute| {
328 attribute.path().is_ident("ast")
329 && attribute
330 .parse_args::<syn::Ident>()
331 .is_ok_and(|ident| ident == "skip")
332 })
333}