blob: fabe598107316cef66ef46560c035d368a76c0a5 [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 Tolnayf1e57f02019-05-08 15:30:39 -07006use syn_codegen as types;
7
8const FOLD_SRC: &str = "../src/gen/fold.rs";
9
David Tolnay2fb3d482019-05-08 16:22:10 -070010#[derive(Default)]
11struct State {
12 fold_trait: TokenStream,
13 fold_impl: TokenStream,
14}
David Tolnayf1e57f02019-05-08 15:30:39 -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}
19
20fn simple_visit(item: &str, name: &TokenStream) -> TokenStream {
21 let ident = under_name(item);
22
23 let method = Ident::new(&format!("fold_{}", ident), Span::call_site());
24 quote! {
25 _visitor.#method(#name)
26 }
27}
28
29fn box_visit(
30 elem: &types::Type,
31 features: &types::Features,
32 defs: &types::Definitions,
33 name: &TokenStream,
34) -> Option<TokenStream> {
35 let res = visit(elem, features, defs, &quote!(*#name))?;
36 Some(quote! {
37 Box::new(#res)
38 })
39}
40
41fn vec_visit(
42 elem: &types::Type,
43 features: &types::Features,
44 defs: &types::Definitions,
45 name: &TokenStream,
46) -> Option<TokenStream> {
47 let operand = quote!(it);
48 let val = visit(elem, features, defs, &operand)?;
49 Some(quote! {
50 FoldHelper::lift(#name, |it| { #val })
51 })
52}
53
54fn punctuated_visit(
55 elem: &types::Type,
56 features: &types::Features,
57 defs: &types::Definitions,
58 name: &TokenStream,
59) -> Option<TokenStream> {
60 let operand = quote!(it);
61 let val = visit(elem, features, defs, &operand)?;
62 Some(quote! {
63 FoldHelper::lift(#name, |it| { #val })
64 })
65}
66
67fn option_visit(
68 elem: &types::Type,
69 features: &types::Features,
70 defs: &types::Definitions,
71 name: &TokenStream,
72) -> Option<TokenStream> {
73 let it = quote!(it);
74 let val = visit(elem, features, defs, &it)?;
75 Some(quote! {
76 (#name).map(|it| { #val })
77 })
78}
79
80fn tuple_visit(
81 elems: &[types::Type],
82 features: &types::Features,
83 defs: &types::Definitions,
84 name: &TokenStream,
85) -> Option<TokenStream> {
86 if elems.is_empty() {
87 return None;
David Tolnayf1e57f02019-05-08 15:30:39 -070088 }
89
David Tolnay2fb3d482019-05-08 16:22:10 -070090 let mut code = TokenStream::new();
91 for (i, elem) in elems.iter().enumerate() {
92 let i = Index::from(i);
93 let it = quote!((#name).#i);
94 let val = visit(elem, features, defs, &it).unwrap_or(it);
95 code.append_all(val);
96 code.append_all(quote!(,));
David Tolnayf1e57f02019-05-08 15:30:39 -070097 }
David Tolnay2fb3d482019-05-08 16:22:10 -070098 Some(quote! {
99 (#code)
100 })
101}
David Tolnayf1e57f02019-05-08 15:30:39 -0700102
David Tolnay2fb3d482019-05-08 16:22:10 -0700103fn token_punct_visit(repr: &str, name: &TokenStream) -> TokenStream {
104 let ty: TokenStream = syn::parse_str(&format!("Token![{}]", repr)).unwrap();
105 quote! {
106 #ty(tokens_helper(_visitor, &#name.spans))
107 }
108}
David Tolnayf1e57f02019-05-08 15:30:39 -0700109
David Tolnay2fb3d482019-05-08 16:22:10 -0700110fn token_keyword_visit(repr: &str, name: &TokenStream) -> TokenStream {
111 let ty: TokenStream = syn::parse_str(&format!("Token![{}]", repr)).unwrap();
112 quote! {
113 #ty(tokens_helper(_visitor, &#name.span))
114 }
115}
116
117fn token_group_visit(ty: &str, name: &TokenStream) -> TokenStream {
118 let ty = Ident::new(ty, Span::call_site());
119 quote! {
120 #ty(tokens_helper(_visitor, &#name.span))
121 }
122}
123
124fn visit(
125 ty: &types::Type,
126 features: &types::Features,
127 defs: &types::Definitions,
128 name: &TokenStream,
129) -> Option<TokenStream> {
130 match ty {
131 types::Type::Box(t) => box_visit(&*t, features, defs, name),
132 types::Type::Vec(t) => vec_visit(&*t, features, defs, name),
133 types::Type::Punctuated(p) => punctuated_visit(&p.element, features, defs, name),
134 types::Type::Option(t) => option_visit(&*t, features, defs, name),
135 types::Type::Tuple(t) => tuple_visit(t, features, defs, name),
136 types::Type::Token(t) => {
137 let repr = &defs.tokens[t];
138 let is_keyword = repr.chars().next().unwrap().is_alphabetic();
139 if is_keyword {
140 Some(token_keyword_visit(repr, name))
141 } else {
142 Some(token_punct_visit(repr, name))
143 }
David Tolnayf1e57f02019-05-08 15:30:39 -0700144 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700145 types::Type::Group(t) => Some(token_group_visit(&t[..], name)),
146 types::Type::Syn(t) => {
147 fn requires_full(features: &types::Features) -> bool {
148 features.any.contains("full") && features.any.len() == 1
149 }
David Tolnayf1e57f02019-05-08 15:30:39 -0700150
David Tolnay2fb3d482019-05-08 16:22:10 -0700151 let res = simple_visit(t, name);
David Tolnayf1e57f02019-05-08 15:30:39 -0700152
David Tolnay2fb3d482019-05-08 16:22:10 -0700153 let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
David Tolnayf1e57f02019-05-08 15:30:39 -0700154
David Tolnay2fb3d482019-05-08 16:22:10 -0700155 Some(
156 if requires_full(&target.features) && !requires_full(features) {
157 quote! {
158 full!(#res)
159 }
David Tolnayf1e57f02019-05-08 15:30:39 -0700160 } else {
David Tolnay2fb3d482019-05-08 16:22:10 -0700161 res
162 },
163 )
David Tolnayf1e57f02019-05-08 15:30:39 -0700164 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700165 types::Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)),
166 types::Type::Ext(_) | types::Type::Std(_) => None,
David Tolnayf1e57f02019-05-08 15:30:39 -0700167 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700168}
David Tolnayf1e57f02019-05-08 15:30:39 -0700169
David Tolnay2fb3d482019-05-08 16:22:10 -0700170fn visit_features(features: &types::Features) -> TokenStream {
171 let features = &features.any;
172 match features.len() {
173 0 => quote!(),
174 1 => quote!(#[cfg(feature = #(#features)*)]),
175 _ => quote!(#[cfg(any(#(feature = #features),*))]),
David Tolnayf1e57f02019-05-08 15:30:39 -0700176 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700177}
David Tolnayf1e57f02019-05-08 15:30:39 -0700178
David Tolnay2fb3d482019-05-08 16:22:10 -0700179fn node(state: &mut State, s: &types::Node, defs: &types::Definitions) {
180 let features = visit_features(&s.features);
181 let under_name = under_name(&s.ident);
182 let ty = Ident::new(&s.ident, Span::call_site());
183 let fold_fn = Ident::new(&format!("fold_{}", under_name), Span::call_site());
David Tolnayf1e57f02019-05-08 15:30:39 -0700184
David Tolnay2fb3d482019-05-08 16:22:10 -0700185 let mut fold_impl = TokenStream::new();
David Tolnayf1e57f02019-05-08 15:30:39 -0700186
David Tolnay2fb3d482019-05-08 16:22:10 -0700187 match &s.data {
188 types::Data::Enum(variants) => {
189 let mut fold_variants = TokenStream::new();
David Tolnayf1e57f02019-05-08 15:30:39 -0700190
David Tolnay2fb3d482019-05-08 16:22:10 -0700191 for (variant, fields) in variants {
192 let variant_ident = Ident::new(variant, Span::call_site());
David Tolnayf1e57f02019-05-08 15:30:39 -0700193
David Tolnay2fb3d482019-05-08 16:22:10 -0700194 if fields.is_empty() {
195 fold_variants.append_all(quote! {
196 #ty::#variant_ident => {
197 #ty::#variant_ident
David Tolnayf1e57f02019-05-08 15:30:39 -0700198 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700199 });
200 } else {
201 let mut bind_fold_fields = TokenStream::new();
202 let mut fold_fields = TokenStream::new();
David Tolnayf1e57f02019-05-08 15:30:39 -0700203
David Tolnay2fb3d482019-05-08 16:22:10 -0700204 for (idx, ty) in fields.iter().enumerate() {
205 let name = format!("_binding_{}", idx);
206 let binding = Ident::new(&name, Span::call_site());
207
208 bind_fold_fields.append_all(quote! {
209 #binding,
David Tolnayf1e57f02019-05-08 15:30:39 -0700210 });
David Tolnayf1e57f02019-05-08 15:30:39 -0700211
David Tolnay2fb3d482019-05-08 16:22:10 -0700212 let owned_binding = quote!(#binding);
213
214 fold_fields.append_all(
215 visit(ty, &s.features, defs, &owned_binding).unwrap_or(owned_binding),
216 );
217
218 fold_fields.append_all(quote!(,));
David Tolnayf1e57f02019-05-08 15:30:39 -0700219 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700220
221 fold_variants.append_all(quote! {
222 #ty::#variant_ident(#bind_fold_fields) => {
223 #ty::#variant_ident(
224 #fold_fields
225 )
226 }
227 });
228 }
229 }
230
231 fold_impl.append_all(quote! {
232 match _i {
233 #fold_variants
234 }
235 });
236 }
237 types::Data::Struct(fields) => {
238 let mut fold_fields = TokenStream::new();
239
240 for (field, ty) in fields {
241 let id = Ident::new(&field, Span::call_site());
242 let ref_toks = quote!(_i.#id);
243 let fold = visit(&ty, &s.features, defs, &ref_toks).unwrap_or(ref_toks);
244
245 fold_fields.append_all(quote! {
246 #id: #fold,
David Tolnayf1e57f02019-05-08 15:30:39 -0700247 });
248 }
David Tolnayf1e57f02019-05-08 15:30:39 -0700249
David Tolnay2fb3d482019-05-08 16:22:10 -0700250 if !fields.is_empty() {
251 fold_impl.append_all(quote! {
252 #ty {
253 #fold_fields
David Tolnayf1e57f02019-05-08 15:30:39 -0700254 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700255 })
256 } else {
David Tolnayf1e57f02019-05-08 15:30:39 -0700257 if ty == "Ident" {
258 fold_impl.append_all(quote! {
259 let mut _i = _i;
260 let span = _visitor.fold_span(_i.span());
261 _i.set_span(span);
262 });
263 }
264 fold_impl.append_all(quote! {
265 _i
266 });
267 }
268 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700269 types::Data::Private => {
270 if ty == "Ident" {
271 fold_impl.append_all(quote! {
272 let mut _i = _i;
273 let span = _visitor.fold_span(_i.span());
274 _i.set_span(span);
275 });
David Tolnayf1e57f02019-05-08 15:30:39 -0700276 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700277 fold_impl.append_all(quote! {
278 _i
David Tolnayf1e57f02019-05-08 15:30:39 -0700279 });
280 }
281 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700282
283 let include_fold_impl = match &s.data {
284 types::Data::Private => gen::TERMINAL_TYPES.contains(&s.ident.as_str()),
285 types::Data::Struct(_) | types::Data::Enum(_) => true,
286 };
287
288 state.fold_trait.append_all(quote! {
289 #features
290 fn #fold_fn(&mut self, i: #ty) -> #ty {
291 #fold_fn(self, i)
292 }
293 });
294
295 if include_fold_impl {
296 state.fold_impl.append_all(quote! {
297 #features
298 pub fn #fold_fn<V: Fold + ?Sized>(
299 _visitor: &mut V, _i: #ty
300 ) -> #ty {
301 #fold_impl
302 }
303 });
304 }
David Tolnayf1e57f02019-05-08 15:30:39 -0700305}
306
David Tolnayf1e57f02019-05-08 15:30:39 -0700307pub fn generate(defs: &types::Definitions) {
David Tolnay2fb3d482019-05-08 16:22:10 -0700308 let state = gen::traverse(defs, node);
David Tolnay1db3d8e2019-05-08 16:10:25 -0700309 let full_macro = full::get_macro();
David Tolnayf1e57f02019-05-08 15:30:39 -0700310 let fold_trait = state.fold_trait;
311 let fold_impl = state.fold_impl;
312 file::write(
313 FOLD_SRC,
314 quote! {
315 // Unreachable code is generated sometimes without the full feature.
316 #![allow(unreachable_code)]
317
318 use *;
319 #[cfg(any(feature = "full", feature = "derive"))]
320 use token::{Brace, Bracket, Paren, Group};
321 use proc_macro2::Span;
322 #[cfg(any(feature = "full", feature = "derive"))]
323 use gen::helper::fold::*;
324
325 #full_macro
326
327 /// Syntax tree traversal to transform the nodes of an owned syntax tree.
328 ///
329 /// See the [module documentation] for details.
330 ///
331 /// [module documentation]: index.html
332 ///
333 /// *This trait is available if Syn is built with the `"fold"` feature.*
334 pub trait Fold {
335 #fold_trait
336 }
337
338 #[cfg(any(feature = "full", feature = "derive"))]
339 macro_rules! fold_span_only {
340 ($f:ident : $t:ident) => {
341 pub fn $f<V: Fold + ?Sized>(_visitor: &mut V, mut _i: $t) -> $t {
342 let span = _visitor.fold_span(_i.span());
343 _i.set_span(span);
344 _i
345 }
346 }
347 }
348
349 #[cfg(any(feature = "full", feature = "derive"))]
350 fold_span_only!(fold_lit_byte: LitByte);
351 #[cfg(any(feature = "full", feature = "derive"))]
352 fold_span_only!(fold_lit_byte_str: LitByteStr);
353 #[cfg(any(feature = "full", feature = "derive"))]
354 fold_span_only!(fold_lit_char: LitChar);
355 #[cfg(any(feature = "full", feature = "derive"))]
356 fold_span_only!(fold_lit_float: LitFloat);
357 #[cfg(any(feature = "full", feature = "derive"))]
358 fold_span_only!(fold_lit_int: LitInt);
359 #[cfg(any(feature = "full", feature = "derive"))]
360 fold_span_only!(fold_lit_str: LitStr);
361
362 #fold_impl
363 },
364 );
365}