blob: f287945e26cefa55f57b1a94213735bcfd5e4c06 [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 Tolnay490b91b2019-05-08 15:46:09 -07006use syn_codegen as types;
7
8const VISIT_SRC: &str = "../src/gen/visit.rs";
9
David Tolnay2fb3d482019-05-08 16:22:10 -070010#[derive(Default)]
11struct State {
12 visit_trait: TokenStream,
13 visit_impl: TokenStream,
14}
David Tolnay490b91b2019-05-08 15:46:09 -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}
David Tolnay490b91b2019-05-08 15:46:09 -070019
David Tolnay2fb3d482019-05-08 16:22:10 -070020enum Operand {
21 Borrowed(TokenStream),
22 Owned(TokenStream),
23}
David Tolnay490b91b2019-05-08 15:46:09 -070024
David Tolnay2fb3d482019-05-08 16:22:10 -070025use self::Operand::*;
David Tolnay490b91b2019-05-08 15:46:09 -070026
David Tolnay2fb3d482019-05-08 16:22:10 -070027impl Operand {
28 fn tokens(&self) -> &TokenStream {
29 match *self {
30 Borrowed(ref n) | Owned(ref n) => n,
David Tolnay490b91b2019-05-08 15:46:09 -070031 }
32 }
33
David Tolnay2fb3d482019-05-08 16:22:10 -070034 fn ref_tokens(&self) -> TokenStream {
35 match *self {
36 Borrowed(ref n) => n.clone(),
37 Owned(ref n) => quote!(&#n),
David Tolnay490b91b2019-05-08 15:46:09 -070038 }
39 }
40
David Tolnay2fb3d482019-05-08 16:22:10 -070041 fn owned_tokens(&self) -> TokenStream {
42 match *self {
43 Borrowed(ref n) => quote!(*#n),
44 Owned(ref n) => n.clone(),
David Tolnay490b91b2019-05-08 15:46:09 -070045 }
David Tolnay490b91b2019-05-08 15:46:09 -070046 }
47}
48
David Tolnay2fb3d482019-05-08 16:22:10 -070049fn simple_visit(item: &str, name: &Operand) -> TokenStream {
50 let ident = under_name(item);
51 let method = Ident::new(&format!("visit_{}", ident), Span::call_site());
52 let name = name.ref_tokens();
53 quote! {
54 _visitor.#method(#name)
55 }
56}
57
David Tolnay2fb3d482019-05-08 16:22:10 -070058fn noop_visit(name: &Operand) -> TokenStream {
59 let name = name.tokens();
60 quote! {
61 skip!(#name)
62 }
63}
64
65fn visit(
66 ty: &types::Type,
67 features: &types::Features,
68 defs: &types::Definitions,
69 name: &Operand,
70) -> Option<TokenStream> {
71 match ty {
David Tolnaycd680542019-05-08 16:29:47 -070072 types::Type::Box(t) => {
73 let name = name.owned_tokens();
74 visit(t, features, defs, &Owned(quote!(*#name)))
75 }
76 types::Type::Vec(t) => {
77 let operand = Borrowed(quote!(it));
78 let val = visit(t, features, defs, &operand)?;
79 let name = name.ref_tokens();
80 Some(quote! {
81 for it in #name {
82 #val
83 }
84 })
85 }
86 types::Type::Punctuated(p) => {
87 let operand = Borrowed(quote!(it));
88 let val = visit(&p.element, features, defs, &operand)?;
89 let name = name.ref_tokens();
90 Some(quote! {
91 for el in Punctuated::pairs(#name) {
92 let it = el.value();
93 #val
94 }
95 })
96 }
97 types::Type::Option(t) => {
98 let it = Borrowed(quote!(it));
99 let val = visit(t, features, defs, &it)?;
100 let name = name.owned_tokens();
101 Some(quote! {
102 if let Some(ref it) = #name {
103 #val
104 }
105 })
106 }
107 types::Type::Tuple(t) => {
108 let mut code = TokenStream::new();
109 for (i, elem) in t.iter().enumerate() {
110 let name = name.tokens();
111 let i = Index::from(i);
112 let it = Owned(quote!((#name).#i));
113 let val = visit(elem, features, defs, &it).unwrap_or_else(|| noop_visit(&it));
114 code.append_all(val);
115 code.append_all(quote!(;));
116 }
117 Some(code)
118 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700119 types::Type::Token(t) => {
David Tolnaycd680542019-05-08 16:29:47 -0700120 let name = name.tokens();
David Tolnay2fb3d482019-05-08 16:22:10 -0700121 let repr = &defs.tokens[t];
122 let is_keyword = repr.chars().next().unwrap().is_alphabetic();
David Tolnaycd680542019-05-08 16:29:47 -0700123 let spans = if is_keyword {
124 quote!(span)
David Tolnay2fb3d482019-05-08 16:22:10 -0700125 } else {
David Tolnaycd680542019-05-08 16:29:47 -0700126 quote!(spans)
127 };
128 Some(quote! {
129 tokens_helper(_visitor, &#name.#spans)
130 })
David Tolnay2fb3d482019-05-08 16:22:10 -0700131 }
David Tolnaycd680542019-05-08 16:29:47 -0700132 types::Type::Group(_) => {
133 let name = name.tokens();
134 Some(quote! {
135 tokens_helper(_visitor, &#name.span)
136 })
137 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700138 types::Type::Syn(t) => {
139 fn requires_full(features: &types::Features) -> bool {
140 features.any.contains("full") && features.any.len() == 1
141 }
David Tolnaycd680542019-05-08 16:29:47 -0700142 let mut res = simple_visit(t, name);
David Tolnay2fb3d482019-05-08 16:22:10 -0700143 let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
David Tolnaycd680542019-05-08 16:29:47 -0700144 if requires_full(&target.features) && !requires_full(features) {
145 res = quote!(full!(#res));
146 }
147 Some(res)
David Tolnay2fb3d482019-05-08 16:22:10 -0700148 }
149 types::Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)),
150 types::Type::Ext(_) | types::Type::Std(_) => None,
151 }
152}
153
154fn visit_features(features: &types::Features) -> TokenStream {
155 let features = &features.any;
156 match features.len() {
157 0 => quote!(),
158 1 => quote!(#[cfg(feature = #(#features)*)]),
159 _ => quote!(#[cfg(any(#(feature = #features),*))]),
160 }
161}
162
163fn node(state: &mut State, s: &types::Node, defs: &types::Definitions) {
164 let features = visit_features(&s.features);
165 let under_name = under_name(&s.ident);
166 let ty = Ident::new(&s.ident, Span::call_site());
167 let visit_fn = Ident::new(&format!("visit_{}", under_name), Span::call_site());
168
169 let mut visit_impl = TokenStream::new();
170
171 match &s.data {
172 types::Data::Enum(variants) => {
173 let mut visit_variants = TokenStream::new();
174
175 for (variant, fields) in variants {
176 let variant_ident = Ident::new(variant, Span::call_site());
177
178 if fields.is_empty() {
179 visit_variants.append_all(quote! {
180 #ty::#variant_ident => {}
181 });
182 } else {
183 let mut bind_visit_fields = TokenStream::new();
184 let mut visit_fields = TokenStream::new();
185
186 for (idx, ty) in fields.iter().enumerate() {
187 let name = format!("_binding_{}", idx);
188 let binding = Ident::new(&name, Span::call_site());
189
190 bind_visit_fields.append_all(quote! {
191 ref #binding,
192 });
193
194 let borrowed_binding = Borrowed(quote!(#binding));
195
196 visit_fields.append_all(
197 visit(ty, &s.features, defs, &borrowed_binding)
198 .unwrap_or_else(|| noop_visit(&borrowed_binding)),
199 );
200
201 visit_fields.append_all(quote!(;));
202 }
203
204 visit_variants.append_all(quote! {
205 #ty::#variant_ident(#bind_visit_fields) => {
206 #visit_fields
207 }
208 });
209 }
210 }
211
212 visit_impl.append_all(quote! {
213 match *_i {
214 #visit_variants
215 }
216 });
217 }
218 types::Data::Struct(fields) => {
219 for (field, ty) in fields {
220 let id = Ident::new(&field, Span::call_site());
221 let ref_toks = Owned(quote!(_i.#id));
222 let visit_field = visit(&ty, &s.features, defs, &ref_toks)
223 .unwrap_or_else(|| noop_visit(&ref_toks));
224 visit_impl.append_all(quote! {
225 #visit_field;
226 });
227 }
228 }
229 types::Data::Private => {}
230 }
231
232 state.visit_trait.append_all(quote! {
233 #features
234 fn #visit_fn(&mut self, i: &'ast #ty) {
235 #visit_fn(self, i)
236 }
237 });
238
239 state.visit_impl.append_all(quote! {
240 #features
241 pub fn #visit_fn<'ast, V: Visit<'ast> + ?Sized>(
242 _visitor: &mut V, _i: &'ast #ty
243 ) {
244 #visit_impl
245 }
246 });
247}
248
David Tolnay490b91b2019-05-08 15:46:09 -0700249pub fn generate(defs: &types::Definitions) {
David Tolnay2fb3d482019-05-08 16:22:10 -0700250 let state = gen::traverse(defs, node);
David Tolnay1db3d8e2019-05-08 16:10:25 -0700251 let full_macro = full::get_macro();
David Tolnay490b91b2019-05-08 15:46:09 -0700252 let visit_trait = state.visit_trait;
253 let visit_impl = state.visit_impl;
254 file::write(
255 VISIT_SRC,
256 quote! {
257 #![cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))]
258
259 use *;
260 #[cfg(any(feature = "full", feature = "derive"))]
261 use punctuated::Punctuated;
262 use proc_macro2::Span;
263 #[cfg(any(feature = "full", feature = "derive"))]
264 use gen::helper::visit::*;
265
266 #full_macro
David Tolnay1db3d8e2019-05-08 16:10:25 -0700267
268 #[cfg(any(feature = "full", feature = "derive"))]
269 macro_rules! skip {
270 ($($tt:tt)*) => {};
271 }
David Tolnay490b91b2019-05-08 15:46:09 -0700272
273 /// Syntax tree traversal to walk a shared borrow of a syntax tree.
274 ///
275 /// See the [module documentation] for details.
276 ///
277 /// [module documentation]: index.html
278 ///
279 /// *This trait is available if Syn is built with the `"visit"` feature.*
280 pub trait Visit<'ast> {
281 #visit_trait
282 }
283
284 #visit_impl
285 },
286 );
287}