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