blob: 15888d175c3bdf78bd02199bff191633443bb0b8 [file] [log] [blame]
David Tolnaye627fa82019-05-08 16:58:01 -07001use crate::operand::*;
David Tolnay1e99fa32019-05-08 16:18:36 -07002use crate::{file, full, gen};
David Tolnay2fb3d482019-05-08 16:22:10 -07003use 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 simple_visit(item: &str, name: &Operand) -> TokenStream {
David Tolnay9ee641a2019-05-08 17:00:16 -070017 let ident = gen::under_name(item);
David Tolnay2fb3d482019-05-08 16:22:10 -070018 let method = Ident::new(&format!("visit_{}", ident), Span::call_site());
19 let name = name.ref_tokens();
20 quote! {
21 _visitor.#method(#name)
22 }
23}
24
David Tolnay2fb3d482019-05-08 16:22:10 -070025fn noop_visit(name: &Operand) -> TokenStream {
26 let name = name.tokens();
27 quote! {
28 skip!(#name)
29 }
30}
31
32fn visit(
33 ty: &types::Type,
34 features: &types::Features,
35 defs: &types::Definitions,
36 name: &Operand,
37) -> Option<TokenStream> {
38 match ty {
David Tolnaycd680542019-05-08 16:29:47 -070039 types::Type::Box(t) => {
40 let name = name.owned_tokens();
41 visit(t, features, defs, &Owned(quote!(*#name)))
42 }
43 types::Type::Vec(t) => {
44 let operand = Borrowed(quote!(it));
45 let val = visit(t, features, defs, &operand)?;
46 let name = name.ref_tokens();
47 Some(quote! {
48 for it in #name {
49 #val
50 }
51 })
52 }
53 types::Type::Punctuated(p) => {
54 let operand = Borrowed(quote!(it));
55 let val = visit(&p.element, features, defs, &operand)?;
56 let name = name.ref_tokens();
57 Some(quote! {
58 for el in Punctuated::pairs(#name) {
59 let it = el.value();
60 #val
61 }
62 })
63 }
64 types::Type::Option(t) => {
65 let it = Borrowed(quote!(it));
66 let val = visit(t, features, defs, &it)?;
67 let name = name.owned_tokens();
68 Some(quote! {
69 if let Some(ref it) = #name {
70 #val
71 }
72 })
73 }
74 types::Type::Tuple(t) => {
75 let mut code = TokenStream::new();
76 for (i, elem) in t.iter().enumerate() {
77 let name = name.tokens();
78 let i = Index::from(i);
79 let it = Owned(quote!((#name).#i));
80 let val = visit(elem, features, defs, &it).unwrap_or_else(|| noop_visit(&it));
81 code.append_all(val);
82 code.append_all(quote!(;));
83 }
84 Some(code)
85 }
David Tolnay2fb3d482019-05-08 16:22:10 -070086 types::Type::Token(t) => {
David Tolnaycd680542019-05-08 16:29:47 -070087 let name = name.tokens();
David Tolnay2fb3d482019-05-08 16:22:10 -070088 let repr = &defs.tokens[t];
89 let is_keyword = repr.chars().next().unwrap().is_alphabetic();
David Tolnaycd680542019-05-08 16:29:47 -070090 let spans = if is_keyword {
91 quote!(span)
David Tolnay2fb3d482019-05-08 16:22:10 -070092 } else {
David Tolnaycd680542019-05-08 16:29:47 -070093 quote!(spans)
94 };
95 Some(quote! {
96 tokens_helper(_visitor, &#name.#spans)
97 })
David Tolnay2fb3d482019-05-08 16:22:10 -070098 }
David Tolnaycd680542019-05-08 16:29:47 -070099 types::Type::Group(_) => {
100 let name = name.tokens();
101 Some(quote! {
102 tokens_helper(_visitor, &#name.span)
103 })
104 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700105 types::Type::Syn(t) => {
106 fn requires_full(features: &types::Features) -> bool {
107 features.any.contains("full") && features.any.len() == 1
108 }
David Tolnaycd680542019-05-08 16:29:47 -0700109 let mut res = simple_visit(t, name);
David Tolnay2fb3d482019-05-08 16:22:10 -0700110 let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
David Tolnaycd680542019-05-08 16:29:47 -0700111 if requires_full(&target.features) && !requires_full(features) {
112 res = quote!(full!(#res));
113 }
114 Some(res)
David Tolnay2fb3d482019-05-08 16:22:10 -0700115 }
116 types::Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)),
117 types::Type::Ext(_) | types::Type::Std(_) => None,
118 }
119}
120
121fn visit_features(features: &types::Features) -> TokenStream {
122 let features = &features.any;
123 match features.len() {
124 0 => quote!(),
125 1 => quote!(#[cfg(feature = #(#features)*)]),
126 _ => quote!(#[cfg(any(#(feature = #features),*))]),
127 }
128}
129
130fn node(state: &mut State, s: &types::Node, defs: &types::Definitions) {
131 let features = visit_features(&s.features);
David Tolnay9ee641a2019-05-08 17:00:16 -0700132 let under_name = gen::under_name(&s.ident);
David Tolnay2fb3d482019-05-08 16:22:10 -0700133 let ty = Ident::new(&s.ident, Span::call_site());
134 let visit_fn = Ident::new(&format!("visit_{}", under_name), Span::call_site());
135
136 let mut visit_impl = TokenStream::new();
137
138 match &s.data {
139 types::Data::Enum(variants) => {
140 let mut visit_variants = TokenStream::new();
141
142 for (variant, fields) in variants {
143 let variant_ident = Ident::new(variant, Span::call_site());
144
145 if fields.is_empty() {
146 visit_variants.append_all(quote! {
147 #ty::#variant_ident => {}
148 });
149 } else {
150 let mut bind_visit_fields = TokenStream::new();
151 let mut visit_fields = TokenStream::new();
152
153 for (idx, ty) in fields.iter().enumerate() {
154 let name = format!("_binding_{}", idx);
155 let binding = Ident::new(&name, Span::call_site());
156
157 bind_visit_fields.append_all(quote! {
158 ref #binding,
159 });
160
161 let borrowed_binding = Borrowed(quote!(#binding));
162
163 visit_fields.append_all(
164 visit(ty, &s.features, defs, &borrowed_binding)
165 .unwrap_or_else(|| noop_visit(&borrowed_binding)),
166 );
167
168 visit_fields.append_all(quote!(;));
169 }
170
171 visit_variants.append_all(quote! {
172 #ty::#variant_ident(#bind_visit_fields) => {
173 #visit_fields
174 }
175 });
176 }
177 }
178
179 visit_impl.append_all(quote! {
180 match *_i {
181 #visit_variants
182 }
183 });
184 }
185 types::Data::Struct(fields) => {
186 for (field, ty) in fields {
187 let id = Ident::new(&field, Span::call_site());
188 let ref_toks = Owned(quote!(_i.#id));
189 let visit_field = visit(&ty, &s.features, defs, &ref_toks)
190 .unwrap_or_else(|| noop_visit(&ref_toks));
191 visit_impl.append_all(quote! {
192 #visit_field;
193 });
194 }
195 }
196 types::Data::Private => {}
197 }
198
199 state.visit_trait.append_all(quote! {
200 #features
201 fn #visit_fn(&mut self, i: &'ast #ty) {
202 #visit_fn(self, i)
203 }
204 });
205
206 state.visit_impl.append_all(quote! {
207 #features
208 pub fn #visit_fn<'ast, V: Visit<'ast> + ?Sized>(
209 _visitor: &mut V, _i: &'ast #ty
210 ) {
211 #visit_impl
212 }
213 });
214}
215
David Tolnay490b91b2019-05-08 15:46:09 -0700216pub fn generate(defs: &types::Definitions) {
David Tolnay2fb3d482019-05-08 16:22:10 -0700217 let state = gen::traverse(defs, node);
David Tolnay1db3d8e2019-05-08 16:10:25 -0700218 let full_macro = full::get_macro();
David Tolnay490b91b2019-05-08 15:46:09 -0700219 let visit_trait = state.visit_trait;
220 let visit_impl = state.visit_impl;
221 file::write(
222 VISIT_SRC,
223 quote! {
224 #![cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))]
225
226 use *;
227 #[cfg(any(feature = "full", feature = "derive"))]
228 use punctuated::Punctuated;
229 use proc_macro2::Span;
230 #[cfg(any(feature = "full", feature = "derive"))]
231 use gen::helper::visit::*;
232
233 #full_macro
David Tolnay1db3d8e2019-05-08 16:10:25 -0700234
235 #[cfg(any(feature = "full", feature = "derive"))]
236 macro_rules! skip {
237 ($($tt:tt)*) => {};
238 }
David Tolnay490b91b2019-05-08 15:46:09 -0700239
240 /// Syntax tree traversal to walk a shared borrow of a syntax tree.
241 ///
242 /// See the [module documentation] for details.
243 ///
244 /// [module documentation]: index.html
245 ///
246 /// *This trait is available if Syn is built with the `"visit"` feature.*
247 pub trait Visit<'ast> {
248 #visit_trait
249 }
250
251 #visit_impl
252 },
253 );
254}