blob: bb6e37d182a32e7d06e07b8d240085813a1679d7 [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);
David Tolnay2fb3d482019-05-08 16:22:10 -070022 let method = Ident::new(&format!("fold_{}", ident), Span::call_site());
23 quote! {
24 _visitor.#method(#name)
25 }
26}
27
David Tolnay2fb3d482019-05-08 16:22:10 -070028fn visit(
29 ty: &types::Type,
30 features: &types::Features,
31 defs: &types::Definitions,
32 name: &TokenStream,
33) -> Option<TokenStream> {
34 match ty {
David Tolnaycd680542019-05-08 16:29:47 -070035 types::Type::Box(t) => {
36 let res = visit(t, features, defs, &quote!(*#name))?;
37 Some(quote! {
38 Box::new(#res)
39 })
40 }
41 types::Type::Vec(t) => {
42 let operand = quote!(it);
43 let val = visit(t, features, defs, &operand)?;
44 Some(quote! {
45 FoldHelper::lift(#name, |it| { #val })
46 })
47 }
48 types::Type::Punctuated(p) => {
49 let operand = quote!(it);
50 let val = visit(&p.element, features, defs, &operand)?;
51 Some(quote! {
52 FoldHelper::lift(#name, |it| { #val })
53 })
54 }
55 types::Type::Option(t) => {
56 let it = quote!(it);
57 let val = visit(t, features, defs, &it)?;
58 Some(quote! {
59 (#name).map(|it| { #val })
60 })
61 }
62 types::Type::Tuple(t) => {
63 let mut code = TokenStream::new();
64 for (i, elem) in t.iter().enumerate() {
65 let i = Index::from(i);
66 let it = quote!((#name).#i);
67 let val = visit(elem, features, defs, &it).unwrap_or(it);
68 code.append_all(val);
69 code.append_all(quote!(,));
70 }
71 Some(quote! {
72 (#code)
73 })
74 }
David Tolnay2fb3d482019-05-08 16:22:10 -070075 types::Type::Token(t) => {
76 let repr = &defs.tokens[t];
77 let is_keyword = repr.chars().next().unwrap().is_alphabetic();
David Tolnaycd680542019-05-08 16:29:47 -070078 let spans = if is_keyword {
79 quote!(span)
David Tolnay2fb3d482019-05-08 16:22:10 -070080 } else {
David Tolnaycd680542019-05-08 16:29:47 -070081 quote!(spans)
82 };
83 let ty: TokenStream = syn::parse_str(&format!("Token![{}]", repr)).unwrap();
84 Some(quote! {
85 #ty(tokens_helper(_visitor, &#name.#spans))
86 })
David Tolnayf1e57f02019-05-08 15:30:39 -070087 }
David Tolnaycd680542019-05-08 16:29:47 -070088 types::Type::Group(t) => {
89 let ty = Ident::new(t, Span::call_site());
90 Some(quote! {
91 #ty(tokens_helper(_visitor, &#name.span))
92 })
93 }
David Tolnay2fb3d482019-05-08 16:22:10 -070094 types::Type::Syn(t) => {
95 fn requires_full(features: &types::Features) -> bool {
96 features.any.contains("full") && features.any.len() == 1
97 }
David Tolnaycd680542019-05-08 16:29:47 -070098 let mut res = simple_visit(t, name);
David Tolnay2fb3d482019-05-08 16:22:10 -070099 let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
David Tolnaycd680542019-05-08 16:29:47 -0700100 if requires_full(&target.features) && !requires_full(features) {
101 res = quote!(full!(#res));
102 }
103 Some(res)
David Tolnayf1e57f02019-05-08 15:30:39 -0700104 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700105 types::Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)),
106 types::Type::Ext(_) | types::Type::Std(_) => None,
David Tolnayf1e57f02019-05-08 15:30:39 -0700107 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700108}
David Tolnayf1e57f02019-05-08 15:30:39 -0700109
David Tolnay2fb3d482019-05-08 16:22:10 -0700110fn visit_features(features: &types::Features) -> TokenStream {
111 let features = &features.any;
112 match features.len() {
113 0 => quote!(),
114 1 => quote!(#[cfg(feature = #(#features)*)]),
115 _ => quote!(#[cfg(any(#(feature = #features),*))]),
David Tolnayf1e57f02019-05-08 15:30:39 -0700116 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700117}
David Tolnayf1e57f02019-05-08 15:30:39 -0700118
David Tolnay2fb3d482019-05-08 16:22:10 -0700119fn node(state: &mut State, s: &types::Node, defs: &types::Definitions) {
120 let features = visit_features(&s.features);
121 let under_name = under_name(&s.ident);
122 let ty = Ident::new(&s.ident, Span::call_site());
123 let fold_fn = Ident::new(&format!("fold_{}", under_name), Span::call_site());
David Tolnayf1e57f02019-05-08 15:30:39 -0700124
David Tolnay2fb3d482019-05-08 16:22:10 -0700125 let mut fold_impl = TokenStream::new();
David Tolnayf1e57f02019-05-08 15:30:39 -0700126
David Tolnay2fb3d482019-05-08 16:22:10 -0700127 match &s.data {
128 types::Data::Enum(variants) => {
129 let mut fold_variants = TokenStream::new();
David Tolnayf1e57f02019-05-08 15:30:39 -0700130
David Tolnay2fb3d482019-05-08 16:22:10 -0700131 for (variant, fields) in variants {
132 let variant_ident = Ident::new(variant, Span::call_site());
David Tolnayf1e57f02019-05-08 15:30:39 -0700133
David Tolnay2fb3d482019-05-08 16:22:10 -0700134 if fields.is_empty() {
135 fold_variants.append_all(quote! {
136 #ty::#variant_ident => {
137 #ty::#variant_ident
David Tolnayf1e57f02019-05-08 15:30:39 -0700138 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700139 });
140 } else {
141 let mut bind_fold_fields = TokenStream::new();
142 let mut fold_fields = TokenStream::new();
David Tolnayf1e57f02019-05-08 15:30:39 -0700143
David Tolnay2fb3d482019-05-08 16:22:10 -0700144 for (idx, ty) in fields.iter().enumerate() {
145 let name = format!("_binding_{}", idx);
146 let binding = Ident::new(&name, Span::call_site());
147
148 bind_fold_fields.append_all(quote! {
149 #binding,
David Tolnayf1e57f02019-05-08 15:30:39 -0700150 });
David Tolnayf1e57f02019-05-08 15:30:39 -0700151
David Tolnay2fb3d482019-05-08 16:22:10 -0700152 let owned_binding = quote!(#binding);
153
154 fold_fields.append_all(
155 visit(ty, &s.features, defs, &owned_binding).unwrap_or(owned_binding),
156 );
157
158 fold_fields.append_all(quote!(,));
David Tolnayf1e57f02019-05-08 15:30:39 -0700159 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700160
161 fold_variants.append_all(quote! {
162 #ty::#variant_ident(#bind_fold_fields) => {
163 #ty::#variant_ident(
164 #fold_fields
165 )
166 }
167 });
168 }
169 }
170
171 fold_impl.append_all(quote! {
172 match _i {
173 #fold_variants
174 }
175 });
176 }
177 types::Data::Struct(fields) => {
178 let mut fold_fields = TokenStream::new();
179
180 for (field, ty) in fields {
181 let id = Ident::new(&field, Span::call_site());
182 let ref_toks = quote!(_i.#id);
183 let fold = visit(&ty, &s.features, defs, &ref_toks).unwrap_or(ref_toks);
184
185 fold_fields.append_all(quote! {
186 #id: #fold,
David Tolnayf1e57f02019-05-08 15:30:39 -0700187 });
188 }
David Tolnayf1e57f02019-05-08 15:30:39 -0700189
David Tolnay2fb3d482019-05-08 16:22:10 -0700190 if !fields.is_empty() {
191 fold_impl.append_all(quote! {
192 #ty {
193 #fold_fields
David Tolnayf1e57f02019-05-08 15:30:39 -0700194 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700195 })
196 } else {
David Tolnayf1e57f02019-05-08 15:30:39 -0700197 if ty == "Ident" {
198 fold_impl.append_all(quote! {
199 let mut _i = _i;
200 let span = _visitor.fold_span(_i.span());
201 _i.set_span(span);
202 });
203 }
204 fold_impl.append_all(quote! {
205 _i
206 });
207 }
208 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700209 types::Data::Private => {
210 if ty == "Ident" {
211 fold_impl.append_all(quote! {
212 let mut _i = _i;
213 let span = _visitor.fold_span(_i.span());
214 _i.set_span(span);
215 });
David Tolnayf1e57f02019-05-08 15:30:39 -0700216 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700217 fold_impl.append_all(quote! {
218 _i
David Tolnayf1e57f02019-05-08 15:30:39 -0700219 });
220 }
221 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700222
223 let include_fold_impl = match &s.data {
224 types::Data::Private => gen::TERMINAL_TYPES.contains(&s.ident.as_str()),
225 types::Data::Struct(_) | types::Data::Enum(_) => true,
226 };
227
228 state.fold_trait.append_all(quote! {
229 #features
230 fn #fold_fn(&mut self, i: #ty) -> #ty {
231 #fold_fn(self, i)
232 }
233 });
234
235 if include_fold_impl {
236 state.fold_impl.append_all(quote! {
237 #features
238 pub fn #fold_fn<V: Fold + ?Sized>(
239 _visitor: &mut V, _i: #ty
240 ) -> #ty {
241 #fold_impl
242 }
243 });
244 }
David Tolnayf1e57f02019-05-08 15:30:39 -0700245}
246
David Tolnayf1e57f02019-05-08 15:30:39 -0700247pub fn generate(defs: &types::Definitions) {
David Tolnay2fb3d482019-05-08 16:22:10 -0700248 let state = gen::traverse(defs, node);
David Tolnay1db3d8e2019-05-08 16:10:25 -0700249 let full_macro = full::get_macro();
David Tolnayf1e57f02019-05-08 15:30:39 -0700250 let fold_trait = state.fold_trait;
251 let fold_impl = state.fold_impl;
252 file::write(
253 FOLD_SRC,
254 quote! {
255 // Unreachable code is generated sometimes without the full feature.
256 #![allow(unreachable_code)]
257
258 use *;
259 #[cfg(any(feature = "full", feature = "derive"))]
260 use token::{Brace, Bracket, Paren, Group};
261 use proc_macro2::Span;
262 #[cfg(any(feature = "full", feature = "derive"))]
263 use gen::helper::fold::*;
264
265 #full_macro
266
267 /// Syntax tree traversal to transform the nodes of an owned syntax tree.
268 ///
269 /// See the [module documentation] for details.
270 ///
271 /// [module documentation]: index.html
272 ///
273 /// *This trait is available if Syn is built with the `"fold"` feature.*
274 pub trait Fold {
275 #fold_trait
276 }
277
278 #[cfg(any(feature = "full", feature = "derive"))]
279 macro_rules! fold_span_only {
280 ($f:ident : $t:ident) => {
281 pub fn $f<V: Fold + ?Sized>(_visitor: &mut V, mut _i: $t) -> $t {
282 let span = _visitor.fold_span(_i.span());
283 _i.set_span(span);
284 _i
285 }
286 }
287 }
288
289 #[cfg(any(feature = "full", feature = "derive"))]
290 fold_span_only!(fold_lit_byte: LitByte);
291 #[cfg(any(feature = "full", feature = "derive"))]
292 fold_span_only!(fold_lit_byte_str: LitByteStr);
293 #[cfg(any(feature = "full", feature = "derive"))]
294 fold_span_only!(fold_lit_char: LitChar);
295 #[cfg(any(feature = "full", feature = "derive"))]
296 fold_span_only!(fold_lit_float: LitFloat);
297 #[cfg(any(feature = "full", feature = "derive"))]
298 fold_span_only!(fold_lit_int: LitInt);
299 #[cfg(any(feature = "full", feature = "derive"))]
300 fold_span_only!(fold_lit_str: LitStr);
301
302 #fold_impl
303 },
304 );
305}