blob: 24c7f7627d57e630554450541432c7260217e836 [file] [log] [blame]
David Tolnay1e99fa32019-05-08 16:18:36 -07001use crate::{file, full, gen};
David Tolnay2fb3d482019-05-08 16:22:10 -07002use inflections::Inflect;
3use proc_macro2::{Span, TokenStream};
4use quote::{quote, TokenStreamExt};
5use syn::*;
David Tolnay490b91b2019-05-08 15:46:09 -07006use syn_codegen as types;
7
8const VISIT_SRC: &str = "../src/gen/visit.rs";
9
David Tolnay2fb3d482019-05-08 16:22:10 -070010#[derive(Default)]
11struct State {
12 visit_trait: TokenStream,
13 visit_impl: TokenStream,
14}
David Tolnay490b91b2019-05-08 15:46:09 -070015
David Tolnay2fb3d482019-05-08 16:22:10 -070016fn under_name(name: &str) -> Ident {
17 Ident::new(&name.to_snake_case(), Span::call_site())
18}
David Tolnay490b91b2019-05-08 15:46:09 -070019
David Tolnay2fb3d482019-05-08 16:22:10 -070020enum Operand {
21 Borrowed(TokenStream),
22 Owned(TokenStream),
23}
David Tolnay490b91b2019-05-08 15:46:09 -070024
David Tolnay2fb3d482019-05-08 16:22:10 -070025use self::Operand::*;
David Tolnay490b91b2019-05-08 15:46:09 -070026
David Tolnay2fb3d482019-05-08 16:22:10 -070027impl Operand {
28 fn tokens(&self) -> &TokenStream {
29 match *self {
30 Borrowed(ref n) | Owned(ref n) => n,
David Tolnay490b91b2019-05-08 15:46:09 -070031 }
32 }
33
David Tolnay2fb3d482019-05-08 16:22:10 -070034 fn ref_tokens(&self) -> TokenStream {
35 match *self {
36 Borrowed(ref n) => n.clone(),
37 Owned(ref n) => quote!(&#n),
David Tolnay490b91b2019-05-08 15:46:09 -070038 }
39 }
40
David Tolnay2fb3d482019-05-08 16:22:10 -070041 fn owned_tokens(&self) -> TokenStream {
42 match *self {
43 Borrowed(ref n) => quote!(*#n),
44 Owned(ref n) => n.clone(),
David Tolnay490b91b2019-05-08 15:46:09 -070045 }
David Tolnay490b91b2019-05-08 15:46:09 -070046 }
47}
48
David Tolnay2fb3d482019-05-08 16:22:10 -070049fn simple_visit(item: &str, name: &Operand) -> TokenStream {
50 let ident = under_name(item);
51 let method = Ident::new(&format!("visit_{}", ident), Span::call_site());
52 let name = name.ref_tokens();
53 quote! {
54 _visitor.#method(#name)
55 }
56}
57
58fn box_visit(
59 elem: &types::Type,
60 features: &types::Features,
61 defs: &types::Definitions,
62 name: &Operand,
63) -> Option<TokenStream> {
64 let name = name.owned_tokens();
65 let res = visit(elem, features, defs, &Owned(quote!(*#name)))?;
66 Some(res)
67}
68
69fn vec_visit(
70 elem: &types::Type,
71 features: &types::Features,
72 defs: &types::Definitions,
73 name: &Operand,
74) -> Option<TokenStream> {
75 let operand = Borrowed(quote!(it));
76 let val = visit(elem, features, defs, &operand)?;
77 let name = name.ref_tokens();
78 Some(quote! {
79 for it in #name {
80 #val
81 }
82 })
83}
84
85fn punctuated_visit(
86 elem: &types::Type,
87 features: &types::Features,
88 defs: &types::Definitions,
89 name: &Operand,
90) -> Option<TokenStream> {
91 let operand = Borrowed(quote!(it));
92 let val = visit(elem, features, defs, &operand)?;
93 let name = name.ref_tokens();
94 Some(quote! {
95 for el in Punctuated::pairs(#name) {
96 let it = el.value();
97 #val
98 }
99 })
100}
101
102fn option_visit(
103 elem: &types::Type,
104 features: &types::Features,
105 defs: &types::Definitions,
106 name: &Operand,
107) -> Option<TokenStream> {
108 let it = Borrowed(quote!(it));
109 let val = visit(elem, features, defs, &it)?;
110 let name = name.owned_tokens();
111 Some(quote! {
112 if let Some(ref it) = #name {
113 #val
114 }
115 })
116}
117
118fn tuple_visit(
119 elems: &[types::Type],
120 features: &types::Features,
121 defs: &types::Definitions,
122 name: &Operand,
123) -> Option<TokenStream> {
124 if elems.is_empty() {
125 return None;
126 }
127
128 let mut code = TokenStream::new();
129 for (i, elem) in elems.iter().enumerate() {
130 let name = name.tokens();
131 let i = Index::from(i);
132 let it = Owned(quote!((#name).#i));
133 let val = visit(elem, features, defs, &it).unwrap_or_else(|| noop_visit(&it));
134 code.append_all(val);
135 code.append_all(quote!(;));
136 }
137 Some(code)
138}
139
140fn token_punct_visit(name: &Operand) -> TokenStream {
141 let name = name.tokens();
142 quote! {
143 tokens_helper(_visitor, &#name.spans)
144 }
145}
146
147fn token_keyword_visit(name: &Operand) -> TokenStream {
148 let name = name.tokens();
149 quote! {
150 tokens_helper(_visitor, &#name.span)
151 }
152}
153
154fn token_group_visit(name: &Operand) -> TokenStream {
155 let name = name.tokens();
156 quote! {
157 tokens_helper(_visitor, &#name.span)
158 }
159}
160
161fn noop_visit(name: &Operand) -> TokenStream {
162 let name = name.tokens();
163 quote! {
164 skip!(#name)
165 }
166}
167
168fn visit(
169 ty: &types::Type,
170 features: &types::Features,
171 defs: &types::Definitions,
172 name: &Operand,
173) -> Option<TokenStream> {
174 match ty {
175 types::Type::Box(t) => box_visit(&*t, features, defs, name),
176 types::Type::Vec(t) => vec_visit(&*t, features, defs, name),
177 types::Type::Punctuated(p) => punctuated_visit(&p.element, features, defs, name),
178 types::Type::Option(t) => option_visit(&*t, features, defs, name),
179 types::Type::Tuple(t) => tuple_visit(t, features, defs, name),
180 types::Type::Token(t) => {
181 let repr = &defs.tokens[t];
182 let is_keyword = repr.chars().next().unwrap().is_alphabetic();
183 if is_keyword {
184 Some(token_keyword_visit(name))
185 } else {
186 Some(token_punct_visit(name))
187 }
188 }
189 types::Type::Group(_) => Some(token_group_visit(name)),
190 types::Type::Syn(t) => {
191 fn requires_full(features: &types::Features) -> bool {
192 features.any.contains("full") && features.any.len() == 1
193 }
194
195 let res = simple_visit(t, name);
196
197 let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
198
199 Some(
200 if requires_full(&target.features) && !requires_full(features) {
201 quote! {
202 full!(#res)
203 }
204 } else {
205 res
206 },
207 )
208 }
209 types::Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)),
210 types::Type::Ext(_) | types::Type::Std(_) => None,
211 }
212}
213
214fn visit_features(features: &types::Features) -> TokenStream {
215 let features = &features.any;
216 match features.len() {
217 0 => quote!(),
218 1 => quote!(#[cfg(feature = #(#features)*)]),
219 _ => quote!(#[cfg(any(#(feature = #features),*))]),
220 }
221}
222
223fn node(state: &mut State, s: &types::Node, defs: &types::Definitions) {
224 let features = visit_features(&s.features);
225 let under_name = under_name(&s.ident);
226 let ty = Ident::new(&s.ident, Span::call_site());
227 let visit_fn = Ident::new(&format!("visit_{}", under_name), Span::call_site());
228
229 let mut visit_impl = TokenStream::new();
230
231 match &s.data {
232 types::Data::Enum(variants) => {
233 let mut visit_variants = TokenStream::new();
234
235 for (variant, fields) in variants {
236 let variant_ident = Ident::new(variant, Span::call_site());
237
238 if fields.is_empty() {
239 visit_variants.append_all(quote! {
240 #ty::#variant_ident => {}
241 });
242 } else {
243 let mut bind_visit_fields = TokenStream::new();
244 let mut visit_fields = TokenStream::new();
245
246 for (idx, ty) in fields.iter().enumerate() {
247 let name = format!("_binding_{}", idx);
248 let binding = Ident::new(&name, Span::call_site());
249
250 bind_visit_fields.append_all(quote! {
251 ref #binding,
252 });
253
254 let borrowed_binding = Borrowed(quote!(#binding));
255
256 visit_fields.append_all(
257 visit(ty, &s.features, defs, &borrowed_binding)
258 .unwrap_or_else(|| noop_visit(&borrowed_binding)),
259 );
260
261 visit_fields.append_all(quote!(;));
262 }
263
264 visit_variants.append_all(quote! {
265 #ty::#variant_ident(#bind_visit_fields) => {
266 #visit_fields
267 }
268 });
269 }
270 }
271
272 visit_impl.append_all(quote! {
273 match *_i {
274 #visit_variants
275 }
276 });
277 }
278 types::Data::Struct(fields) => {
279 for (field, ty) in fields {
280 let id = Ident::new(&field, Span::call_site());
281 let ref_toks = Owned(quote!(_i.#id));
282 let visit_field = visit(&ty, &s.features, defs, &ref_toks)
283 .unwrap_or_else(|| noop_visit(&ref_toks));
284 visit_impl.append_all(quote! {
285 #visit_field;
286 });
287 }
288 }
289 types::Data::Private => {}
290 }
291
292 state.visit_trait.append_all(quote! {
293 #features
294 fn #visit_fn(&mut self, i: &'ast #ty) {
295 #visit_fn(self, i)
296 }
297 });
298
299 state.visit_impl.append_all(quote! {
300 #features
301 pub fn #visit_fn<'ast, V: Visit<'ast> + ?Sized>(
302 _visitor: &mut V, _i: &'ast #ty
303 ) {
304 #visit_impl
305 }
306 });
307}
308
David Tolnay490b91b2019-05-08 15:46:09 -0700309pub fn generate(defs: &types::Definitions) {
David Tolnay2fb3d482019-05-08 16:22:10 -0700310 let state = gen::traverse(defs, node);
David Tolnay1db3d8e2019-05-08 16:10:25 -0700311 let full_macro = full::get_macro();
David Tolnay490b91b2019-05-08 15:46:09 -0700312 let visit_trait = state.visit_trait;
313 let visit_impl = state.visit_impl;
314 file::write(
315 VISIT_SRC,
316 quote! {
317 #![cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))]
318
319 use *;
320 #[cfg(any(feature = "full", feature = "derive"))]
321 use punctuated::Punctuated;
322 use proc_macro2::Span;
323 #[cfg(any(feature = "full", feature = "derive"))]
324 use gen::helper::visit::*;
325
326 #full_macro
David Tolnay1db3d8e2019-05-08 16:10:25 -0700327
328 #[cfg(any(feature = "full", feature = "derive"))]
329 macro_rules! skip {
330 ($($tt:tt)*) => {};
331 }
David Tolnay490b91b2019-05-08 15:46:09 -0700332
333 /// Syntax tree traversal to walk a shared borrow of a syntax tree.
334 ///
335 /// See the [module documentation] for details.
336 ///
337 /// [module documentation]: index.html
338 ///
339 /// *This trait is available if Syn is built with the `"visit"` feature.*
340 pub trait Visit<'ast> {
341 #visit_trait
342 }
343
344 #visit_impl
345 },
346 );
347}