blob: 9f85df92ed0e59eaa11ea21f6176fd17bc924502 [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 inflections::Inflect;
4use proc_macro2::{Span, TokenStream};
5use quote::{quote, TokenStreamExt};
6use syn::*;
David Tolnay490b91b2019-05-08 15:46:09 -07007use syn_codegen as types;
8
9const VISIT_SRC: &str = "../src/gen/visit.rs";
10
David Tolnay2fb3d482019-05-08 16:22:10 -070011#[derive(Default)]
12struct State {
13 visit_trait: TokenStream,
14 visit_impl: TokenStream,
15}
David Tolnay490b91b2019-05-08 15:46:09 -070016
David Tolnay2fb3d482019-05-08 16:22:10 -070017fn under_name(name: &str) -> Ident {
18 Ident::new(&name.to_snake_case(), Span::call_site())
19}
David Tolnay490b91b2019-05-08 15:46:09 -070020
David Tolnay2fb3d482019-05-08 16:22:10 -070021fn simple_visit(item: &str, name: &Operand) -> TokenStream {
22 let ident = under_name(item);
23 let method = Ident::new(&format!("visit_{}", ident), Span::call_site());
24 let name = name.ref_tokens();
25 quote! {
26 _visitor.#method(#name)
27 }
28}
29
David Tolnay2fb3d482019-05-08 16:22:10 -070030fn noop_visit(name: &Operand) -> TokenStream {
31 let name = name.tokens();
32 quote! {
33 skip!(#name)
34 }
35}
36
37fn visit(
38 ty: &types::Type,
39 features: &types::Features,
40 defs: &types::Definitions,
41 name: &Operand,
42) -> Option<TokenStream> {
43 match ty {
David Tolnaycd680542019-05-08 16:29:47 -070044 types::Type::Box(t) => {
45 let name = name.owned_tokens();
46 visit(t, features, defs, &Owned(quote!(*#name)))
47 }
48 types::Type::Vec(t) => {
49 let operand = Borrowed(quote!(it));
50 let val = visit(t, features, defs, &operand)?;
51 let name = name.ref_tokens();
52 Some(quote! {
53 for it in #name {
54 #val
55 }
56 })
57 }
58 types::Type::Punctuated(p) => {
59 let operand = Borrowed(quote!(it));
60 let val = visit(&p.element, features, defs, &operand)?;
61 let name = name.ref_tokens();
62 Some(quote! {
63 for el in Punctuated::pairs(#name) {
64 let it = el.value();
65 #val
66 }
67 })
68 }
69 types::Type::Option(t) => {
70 let it = Borrowed(quote!(it));
71 let val = visit(t, features, defs, &it)?;
72 let name = name.owned_tokens();
73 Some(quote! {
74 if let Some(ref it) = #name {
75 #val
76 }
77 })
78 }
79 types::Type::Tuple(t) => {
80 let mut code = TokenStream::new();
81 for (i, elem) in t.iter().enumerate() {
82 let name = name.tokens();
83 let i = Index::from(i);
84 let it = Owned(quote!((#name).#i));
85 let val = visit(elem, features, defs, &it).unwrap_or_else(|| noop_visit(&it));
86 code.append_all(val);
87 code.append_all(quote!(;));
88 }
89 Some(code)
90 }
David Tolnay2fb3d482019-05-08 16:22:10 -070091 types::Type::Token(t) => {
David Tolnaycd680542019-05-08 16:29:47 -070092 let name = name.tokens();
David Tolnay2fb3d482019-05-08 16:22:10 -070093 let repr = &defs.tokens[t];
94 let is_keyword = repr.chars().next().unwrap().is_alphabetic();
David Tolnaycd680542019-05-08 16:29:47 -070095 let spans = if is_keyword {
96 quote!(span)
David Tolnay2fb3d482019-05-08 16:22:10 -070097 } else {
David Tolnaycd680542019-05-08 16:29:47 -070098 quote!(spans)
99 };
100 Some(quote! {
101 tokens_helper(_visitor, &#name.#spans)
102 })
David Tolnay2fb3d482019-05-08 16:22:10 -0700103 }
David Tolnaycd680542019-05-08 16:29:47 -0700104 types::Type::Group(_) => {
105 let name = name.tokens();
106 Some(quote! {
107 tokens_helper(_visitor, &#name.span)
108 })
109 }
David Tolnay2fb3d482019-05-08 16:22:10 -0700110 types::Type::Syn(t) => {
111 fn requires_full(features: &types::Features) -> bool {
112 features.any.contains("full") && features.any.len() == 1
113 }
David Tolnaycd680542019-05-08 16:29:47 -0700114 let mut res = simple_visit(t, name);
David Tolnay2fb3d482019-05-08 16:22:10 -0700115 let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
David Tolnaycd680542019-05-08 16:29:47 -0700116 if requires_full(&target.features) && !requires_full(features) {
117 res = quote!(full!(#res));
118 }
119 Some(res)
David Tolnay2fb3d482019-05-08 16:22:10 -0700120 }
121 types::Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)),
122 types::Type::Ext(_) | types::Type::Std(_) => None,
123 }
124}
125
126fn visit_features(features: &types::Features) -> TokenStream {
127 let features = &features.any;
128 match features.len() {
129 0 => quote!(),
130 1 => quote!(#[cfg(feature = #(#features)*)]),
131 _ => quote!(#[cfg(any(#(feature = #features),*))]),
132 }
133}
134
135fn node(state: &mut State, s: &types::Node, defs: &types::Definitions) {
136 let features = visit_features(&s.features);
137 let under_name = under_name(&s.ident);
138 let ty = Ident::new(&s.ident, Span::call_site());
139 let visit_fn = Ident::new(&format!("visit_{}", under_name), Span::call_site());
140
141 let mut visit_impl = TokenStream::new();
142
143 match &s.data {
144 types::Data::Enum(variants) => {
145 let mut visit_variants = TokenStream::new();
146
147 for (variant, fields) in variants {
148 let variant_ident = Ident::new(variant, Span::call_site());
149
150 if fields.is_empty() {
151 visit_variants.append_all(quote! {
152 #ty::#variant_ident => {}
153 });
154 } else {
155 let mut bind_visit_fields = TokenStream::new();
156 let mut visit_fields = TokenStream::new();
157
158 for (idx, ty) in fields.iter().enumerate() {
159 let name = format!("_binding_{}", idx);
160 let binding = Ident::new(&name, Span::call_site());
161
162 bind_visit_fields.append_all(quote! {
163 ref #binding,
164 });
165
166 let borrowed_binding = Borrowed(quote!(#binding));
167
168 visit_fields.append_all(
169 visit(ty, &s.features, defs, &borrowed_binding)
170 .unwrap_or_else(|| noop_visit(&borrowed_binding)),
171 );
172
173 visit_fields.append_all(quote!(;));
174 }
175
176 visit_variants.append_all(quote! {
177 #ty::#variant_ident(#bind_visit_fields) => {
178 #visit_fields
179 }
180 });
181 }
182 }
183
184 visit_impl.append_all(quote! {
185 match *_i {
186 #visit_variants
187 }
188 });
189 }
190 types::Data::Struct(fields) => {
191 for (field, ty) in fields {
192 let id = Ident::new(&field, Span::call_site());
193 let ref_toks = Owned(quote!(_i.#id));
194 let visit_field = visit(&ty, &s.features, defs, &ref_toks)
195 .unwrap_or_else(|| noop_visit(&ref_toks));
196 visit_impl.append_all(quote! {
197 #visit_field;
198 });
199 }
200 }
201 types::Data::Private => {}
202 }
203
204 state.visit_trait.append_all(quote! {
205 #features
206 fn #visit_fn(&mut self, i: &'ast #ty) {
207 #visit_fn(self, i)
208 }
209 });
210
211 state.visit_impl.append_all(quote! {
212 #features
213 pub fn #visit_fn<'ast, V: Visit<'ast> + ?Sized>(
214 _visitor: &mut V, _i: &'ast #ty
215 ) {
216 #visit_impl
217 }
218 });
219}
220
David Tolnay490b91b2019-05-08 15:46:09 -0700221pub fn generate(defs: &types::Definitions) {
David Tolnay2fb3d482019-05-08 16:22:10 -0700222 let state = gen::traverse(defs, node);
David Tolnay1db3d8e2019-05-08 16:10:25 -0700223 let full_macro = full::get_macro();
David Tolnay490b91b2019-05-08 15:46:09 -0700224 let visit_trait = state.visit_trait;
225 let visit_impl = state.visit_impl;
226 file::write(
227 VISIT_SRC,
228 quote! {
229 #![cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))]
230
231 use *;
232 #[cfg(any(feature = "full", feature = "derive"))]
233 use punctuated::Punctuated;
234 use proc_macro2::Span;
235 #[cfg(any(feature = "full", feature = "derive"))]
236 use gen::helper::visit::*;
237
238 #full_macro
David Tolnay1db3d8e2019-05-08 16:10:25 -0700239
240 #[cfg(any(feature = "full", feature = "derive"))]
241 macro_rules! skip {
242 ($($tt:tt)*) => {};
243 }
David Tolnay490b91b2019-05-08 15:46:09 -0700244
245 /// Syntax tree traversal to walk a shared borrow of a syntax tree.
246 ///
247 /// See the [module documentation] for details.
248 ///
249 /// [module documentation]: index.html
250 ///
251 /// *This trait is available if Syn is built with the `"visit"` feature.*
252 pub trait Visit<'ast> {
253 #visit_trait
254 }
255
256 #visit_impl
257 },
258 );
259}