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}