another_visitor_macros/
lib.rs1#![deny(clippy::all)]
2#![warn(clippy::pedantic)]
3#![allow(clippy::missing_panics_doc)] use proc_macro::{self, TokenStream};
6use quote::{format_ident, quote, ToTokens};
7use syn::{parse_macro_input, Attribute, Data, DeriveInput, Ident, Meta, NestedMeta};
8
9#[proc_macro_derive(Visitable, attributes(visit))]
10pub fn derive_visitable(input: TokenStream) -> TokenStream {
11    let DeriveInput { ident, data, .. } = parse_macro_input!(input);
12    let children: proc_macro2::TokenStream = match data {
13        Data::Struct(struct_) => struct_
14            .fields
15            .into_iter()
16            .filter_map(|f| {
17                if should_skip(&f.attrs) {
18                    None
19                } else {
20                    let id = f.ident;
21                    Some(quote! { &self.#id, })
22                }
23            })
24            .collect(),
25        Data::Enum(enum_) => {
26            let matches: proc_macro2::TokenStream = enum_
27                .variants
28                .into_iter()
29                .filter_map(|v| {
30                    if should_skip(&v.attrs) {
31                        None
32                    } else {
33                        let id = v.ident;
34                        Some(quote! {
35                            Self::#id(i) => i,
36                        })
37                    }
38                })
39                .collect();
40            quote! {
41                match self {
42                    #matches
43                }
44            }
45        },
46        Data::Union(_) => panic!("Deriving Visitable for unions is not supported"),
47    };
48    let output = quote! {
49        impl another_visitor::Visitable for #ident {
50            fn children(&self) -> Vec<&dyn another_visitor::Visitable> {
51                vec![#children]
52            }
53        }
54    };
55    output.into()
56}
57
58#[proc_macro_derive(VisitableMut, attributes(visit))]
59pub fn derive_visitable_mut(input: TokenStream) -> TokenStream {
60    let DeriveInput { ident, data, .. } = parse_macro_input!(input);
61    let children: proc_macro2::TokenStream = match data {
62        Data::Struct(struct_) => struct_
63            .fields
64            .into_iter()
65            .filter_map(|f| {
66                if should_skip(&f.attrs) {
67                    None
68                } else {
69                    let id = f.ident;
70                    Some(quote! { &mut self.#id, })
71                }
72            })
73            .collect(),
74        Data::Enum(enum_) => {
75            let matches: proc_macro2::TokenStream = enum_
76                .variants
77                .into_iter()
78                .filter_map(|v| {
79                    if should_skip(&v.attrs) {
80                        None
81                    } else {
82                        let id = v.ident;
83                        Some(quote! {
84                            Self::#id(i) => i,
85                        })
86                    }
87                })
88                .collect();
89            quote! {
90                match self {
91                    #matches
92                }
93            }
94        },
95        Data::Union(_) => panic!("Deriving VisitableMut for unions is not supported"),
96    };
97    let output = quote! {
98        impl another_visitor::VisitableMut for #ident {
99            fn children_mut(&mut self) -> Vec<&mut dyn another_visitor::VisitableMut> {
100                vec![#children]
101            }
102        }
103    };
104    output.into()
105}
106
107fn should_skip(attrs: &[Attribute]) -> bool {
108    attrs.iter().any(|a| {
109        if a.path.segments.len() == 1 && a.path.segments[0].ident == "visit" {
110            if let Ok(Meta::List(mut meta)) = a.parse_meta() {
111                if let NestedMeta::Meta(Meta::Path(p)) = meta.nested.pop().unwrap().into_value() {
112                    return p.segments[0].ident == "skip";
113                }
114                panic!("Invalid use of `visit` attribute");
115            } else {
116                panic!("Invalid use of `visit` attribute");
117            }
118        }
119
120        false
121    })
122}
123
124#[proc_macro_derive(Visitor, attributes(visit))]
125pub fn derive_visitor(input: TokenStream) -> TokenStream {
126    let DeriveInput { ident, attrs, .. } = parse_macro_input!(input);
127
128    let ts = visit_types_from_attrs(&attrs);
129
130    let visit_impls: Vec<proc_macro2::TokenStream> = ts
131        .iter()
132        .map(|(id, full_type)| {
133            let fname = format_ident!("visit_{}", id.to_string().to_lowercase());
134            quote! {
135                if let Some(d) = v.downcast_ref::<#full_type>() {
136                    return self.#fname(d);
137                }
138            }
139        })
140        .collect();
141
142    let output = quote! {
143        impl another_visitor::Visitor for #ident {
144            fn visit(&mut self, v: &dyn another_visitor::Visitable) -> Self::Output {
145                #(#visit_impls)else *
146                self.visit_children(v)
147            }
148        }
149    };
150    output.into()
151}
152
153#[proc_macro_derive(VisitorMut, attributes(visit))]
154pub fn derive_visitor_mut(input: TokenStream) -> TokenStream {
155    let DeriveInput { ident, attrs, .. } = parse_macro_input!(input);
156
157    let ts = visit_types_from_attrs(&attrs);
158
159    let visit_impls: Vec<proc_macro2::TokenStream> = ts
160        .iter()
161        .map(|(id, full_type)| {
162            let fname = format_ident!("visit_{}", id.to_string().to_lowercase());
163            quote! {
164                if let Some(d) = v.downcast_mut::<#full_type>() {
165                    return self.#fname(d);
166                }
167            }
168        })
169        .collect();
170
171    let output = quote! {
172        impl another_visitor::VisitorMut for #ident {
173            fn visit(&mut self, v: &mut dyn another_visitor::VisitableMut) -> Self::Output {
174                #(#visit_impls)else *
175                self.visit_children(v)
176            }
177        }
178    };
179    output.into()
180}
181
182fn visit_types_from_attrs(attrs: &[Attribute]) -> Vec<(Ident, proc_macro2::TokenStream)> {
183    attrs
184        .iter()
185        .filter(|a| a.path.segments.len() == 1 && a.path.segments[0].ident == "visit")
186        .flat_map(|a| {
187            let v: Vec<(Ident, proc_macro2::TokenStream)> =
188                if let Ok(Meta::List(list)) = a.parse_meta() {
189                    list.nested
190                        .iter()
191                        .map(|nested| {
192                            if let NestedMeta::Meta(Meta::Path(p)) = nested {
193                                (
194                                    p.segments.last().unwrap().ident.clone(),
195                                    p.to_token_stream(),
196                                )
197                            } else {
198                                panic!("Invalid usage of `visit` attribute");
199                            }
200                        })
201                        .collect()
202                } else {
203                    panic!("Invalid usage of `visit` attribute")
204                };
205            v
206        })
207        .collect::<Vec<_>>()
208}