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