blob: adcde21d07b5ac1aaa69bf397aa6ad5b1eaeb149 [file] [log] [blame]
David Tolnay1e99fa32019-05-08 16:18:36 -07001use crate::{file, full, gen};
David Tolnayf1e57f02019-05-08 15:30:39 -07002use quote::quote;
3use syn_codegen as types;
4
5const FOLD_SRC: &str = "../src/gen/fold.rs";
6
7mod codegen {
David Tolnay1e99fa32019-05-08 16:18:36 -07008 use crate::gen;
David Tolnayf1e57f02019-05-08 15:30:39 -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 fold_trait: TokenStream,
18 pub fold_impl: TokenStream,
19 }
20
21 fn under_name(name: &str) -> Ident {
22 Ident::new(&name.to_snake_case(), Span::call_site())
23 }
24
25 fn simple_visit(item: &str, name: &TokenStream) -> TokenStream {
26 let ident = under_name(item);
27
28 let method = Ident::new(&format!("fold_{}", ident), Span::call_site());
29 quote! {
30 _visitor.#method(#name)
31 }
32 }
33
34 fn box_visit(
35 elem: &types::Type,
36 features: &types::Features,
37 defs: &types::Definitions,
38 name: &TokenStream,
39 ) -> Option<TokenStream> {
40 let res = visit(elem, features, defs, &quote!(*#name))?;
41 Some(quote! {
42 Box::new(#res)
43 })
44 }
45
46 fn vec_visit(
47 elem: &types::Type,
48 features: &types::Features,
49 defs: &types::Definitions,
50 name: &TokenStream,
51 ) -> Option<TokenStream> {
52 let operand = quote!(it);
53 let val = visit(elem, features, defs, &operand)?;
54 Some(quote! {
55 FoldHelper::lift(#name, |it| { #val })
56 })
57 }
58
59 fn punctuated_visit(
60 elem: &types::Type,
61 features: &types::Features,
62 defs: &types::Definitions,
63 name: &TokenStream,
64 ) -> Option<TokenStream> {
65 let operand = quote!(it);
66 let val = visit(elem, features, defs, &operand)?;
67 Some(quote! {
68 FoldHelper::lift(#name, |it| { #val })
69 })
70 }
71
72 fn option_visit(
73 elem: &types::Type,
74 features: &types::Features,
75 defs: &types::Definitions,
76 name: &TokenStream,
77 ) -> Option<TokenStream> {
78 let it = quote!(it);
79 let val = visit(elem, features, defs, &it)?;
80 Some(quote! {
81 (#name).map(|it| { #val })
82 })
83 }
84
85 fn tuple_visit(
86 elems: &[types::Type],
87 features: &types::Features,
88 defs: &types::Definitions,
89 name: &TokenStream,
90 ) -> Option<TokenStream> {
91 if elems.is_empty() {
92 return None;
93 }
94
95 let mut code = TokenStream::new();
96 for (i, elem) in elems.iter().enumerate() {
97 let i = Index::from(i);
98 let it = quote!((#name).#i);
99 let val = visit(elem, features, defs, &it).unwrap_or(it);
100 code.append_all(val);
101 code.append_all(quote!(,));
102 }
103 Some(quote! {
104 (#code)
105 })
106 }
107
108 fn token_punct_visit(repr: &str, name: &TokenStream) -> TokenStream {
109 let ty: TokenStream = syn::parse_str(&format!("Token![{}]", repr)).unwrap();
110 quote! {
111 #ty(tokens_helper(_visitor, &#name.spans))
112 }
113 }
114
115 fn token_keyword_visit(repr: &str, name: &TokenStream) -> TokenStream {
116 let ty: TokenStream = syn::parse_str(&format!("Token![{}]", repr)).unwrap();
117 quote! {
118 #ty(tokens_helper(_visitor, &#name.span))
119 }
120 }
121
122 fn token_group_visit(ty: &str, name: &TokenStream) -> TokenStream {
123 let ty = Ident::new(ty, Span::call_site());
124 quote! {
125 #ty(tokens_helper(_visitor, &#name.span))
126 }
127 }
128
129 fn visit(
130 ty: &types::Type,
131 features: &types::Features,
132 defs: &types::Definitions,
133 name: &TokenStream,
134 ) -> Option<TokenStream> {
135 match ty {
136 types::Type::Box(t) => box_visit(&*t, features, defs, name),
137 types::Type::Vec(t) => vec_visit(&*t, features, defs, name),
138 types::Type::Punctuated(p) => punctuated_visit(&p.element, features, defs, name),
139 types::Type::Option(t) => option_visit(&*t, features, defs, name),
140 types::Type::Tuple(t) => tuple_visit(t, features, defs, name),
141 types::Type::Token(t) => {
142 let repr = &defs.tokens[t];
143 let is_keyword = repr.chars().next().unwrap().is_alphabetic();
144 if is_keyword {
145 Some(token_keyword_visit(repr, name))
146 } else {
147 Some(token_punct_visit(repr, name))
148 }
149 }
150 types::Type::Group(t) => Some(token_group_visit(&t[..], name)),
151 types::Type::Syn(t) => {
152 fn requires_full(features: &types::Features) -> bool {
153 features.any.contains("full") && features.any.len() == 1
154 }
155
156 let res = simple_visit(t, name);
157
158 let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
159
160 Some(
161 if requires_full(&target.features) && !requires_full(features) {
162 quote! {
163 full!(#res)
164 }
165 } else {
166 res
167 },
168 )
169 }
David Tolnay1e99fa32019-05-08 16:18:36 -0700170 types::Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => {
David Tolnayf1e57f02019-05-08 15:30:39 -0700171 Some(simple_visit(t, name))
172 }
173 types::Type::Ext(_) | types::Type::Std(_) => None,
174 }
175 }
176
177 fn visit_features(features: &types::Features) -> TokenStream {
178 let features = &features.any;
179 match features.len() {
180 0 => quote!(),
181 1 => quote!(#[cfg(feature = #(#features)*)]),
182 _ => quote!(#[cfg(any(#(feature = #features),*))]),
183 }
184 }
185
186 pub fn generate(state: &mut State, s: &types::Node, defs: &types::Definitions) {
187 let features = visit_features(&s.features);
188 let under_name = under_name(&s.ident);
189 let ty = Ident::new(&s.ident, Span::call_site());
190 let fold_fn = Ident::new(&format!("fold_{}", under_name), Span::call_site());
191
192 let mut fold_impl = TokenStream::new();
193
194 match &s.data {
195 types::Data::Enum(variants) => {
196 let mut fold_variants = TokenStream::new();
197
198 for (variant, fields) in variants {
199 let variant_ident = Ident::new(variant, Span::call_site());
200
201 if fields.is_empty() {
202 fold_variants.append_all(quote! {
203 #ty::#variant_ident => {
204 #ty::#variant_ident
205 }
206 });
207 } else {
208 let mut bind_fold_fields = TokenStream::new();
209 let mut fold_fields = TokenStream::new();
210
211 for (idx, ty) in fields.iter().enumerate() {
212 let name = format!("_binding_{}", idx);
213 let binding = Ident::new(&name, Span::call_site());
214
215 bind_fold_fields.append_all(quote! {
216 #binding,
217 });
218
219 let owned_binding = quote!(#binding);
220
221 fold_fields.append_all(
222 visit(ty, &s.features, defs, &owned_binding)
223 .unwrap_or(owned_binding),
224 );
225
226 fold_fields.append_all(quote!(,));
227 }
228
229 fold_variants.append_all(quote! {
230 #ty::#variant_ident(#bind_fold_fields) => {
231 #ty::#variant_ident(
232 #fold_fields
233 )
234 }
235 });
236 }
237 }
238
239 fold_impl.append_all(quote! {
240 match _i {
241 #fold_variants
242 }
243 });
244 }
245 types::Data::Struct(fields) => {
246 let mut fold_fields = TokenStream::new();
247
248 for (field, ty) in fields {
249 let id = Ident::new(&field, Span::call_site());
250 let ref_toks = quote!(_i.#id);
251 let fold = visit(&ty, &s.features, defs, &ref_toks).unwrap_or(ref_toks);
252
253 fold_fields.append_all(quote! {
254 #id: #fold,
255 });
256 }
257
258 if !fields.is_empty() {
259 fold_impl.append_all(quote! {
260 #ty {
261 #fold_fields
262 }
263 })
264 } else {
265 if ty == "Ident" {
266 fold_impl.append_all(quote! {
267 let mut _i = _i;
268 let span = _visitor.fold_span(_i.span());
269 _i.set_span(span);
270 });
271 }
272 fold_impl.append_all(quote! {
273 _i
274 });
275 }
276 }
277 types::Data::Private => {
278 if ty == "Ident" {
279 fold_impl.append_all(quote! {
280 let mut _i = _i;
281 let span = _visitor.fold_span(_i.span());
282 _i.set_span(span);
283 });
284 }
285 fold_impl.append_all(quote! {
286 _i
287 });
288 }
289 }
290
291 let include_fold_impl = match &s.data {
David Tolnay1e99fa32019-05-08 16:18:36 -0700292 types::Data::Private => gen::TERMINAL_TYPES.contains(&s.ident.as_str()),
David Tolnayf1e57f02019-05-08 15:30:39 -0700293 types::Data::Struct(_) | types::Data::Enum(_) => true,
294 };
295
296 state.fold_trait.append_all(quote! {
297 #features
298 fn #fold_fn(&mut self, i: #ty) -> #ty {
299 #fold_fn(self, i)
300 }
301 });
302
303 if include_fold_impl {
304 state.fold_impl.append_all(quote! {
305 #features
306 pub fn #fold_fn<V: Fold + ?Sized>(
307 _visitor: &mut V, _i: #ty
308 ) -> #ty {
309 #fold_impl
310 }
311 });
312 }
313 }
314}
315
David Tolnayf1e57f02019-05-08 15:30:39 -0700316pub fn generate(defs: &types::Definitions) {
David Tolnay1e99fa32019-05-08 16:18:36 -0700317 let state = gen::traverse(defs, codegen::generate);
David Tolnay1db3d8e2019-05-08 16:10:25 -0700318 let full_macro = full::get_macro();
David Tolnayf1e57f02019-05-08 15:30:39 -0700319 let fold_trait = state.fold_trait;
320 let fold_impl = state.fold_impl;
321 file::write(
322 FOLD_SRC,
323 quote! {
324 // Unreachable code is generated sometimes without the full feature.
325 #![allow(unreachable_code)]
326
327 use *;
328 #[cfg(any(feature = "full", feature = "derive"))]
329 use token::{Brace, Bracket, Paren, Group};
330 use proc_macro2::Span;
331 #[cfg(any(feature = "full", feature = "derive"))]
332 use gen::helper::fold::*;
333
334 #full_macro
335
336 /// Syntax tree traversal to transform the nodes of an owned syntax tree.
337 ///
338 /// See the [module documentation] for details.
339 ///
340 /// [module documentation]: index.html
341 ///
342 /// *This trait is available if Syn is built with the `"fold"` feature.*
343 pub trait Fold {
344 #fold_trait
345 }
346
347 #[cfg(any(feature = "full", feature = "derive"))]
348 macro_rules! fold_span_only {
349 ($f:ident : $t:ident) => {
350 pub fn $f<V: Fold + ?Sized>(_visitor: &mut V, mut _i: $t) -> $t {
351 let span = _visitor.fold_span(_i.span());
352 _i.set_span(span);
353 _i
354 }
355 }
356 }
357
358 #[cfg(any(feature = "full", feature = "derive"))]
359 fold_span_only!(fold_lit_byte: LitByte);
360 #[cfg(any(feature = "full", feature = "derive"))]
361 fold_span_only!(fold_lit_byte_str: LitByteStr);
362 #[cfg(any(feature = "full", feature = "derive"))]
363 fold_span_only!(fold_lit_char: LitChar);
364 #[cfg(any(feature = "full", feature = "derive"))]
365 fold_span_only!(fold_lit_float: LitFloat);
366 #[cfg(any(feature = "full", feature = "derive"))]
367 fold_span_only!(fold_lit_int: LitInt);
368 #[cfg(any(feature = "full", feature = "derive"))]
369 fold_span_only!(fold_lit_str: LitStr);
370
371 #fold_impl
372 },
373 );
374}