blob: 2a18244943377a015c3a56cbb054be454d27ad72 [file] [log] [blame]
David Tolnay12597912019-05-08 17:02:44 -07001use crate::operand::{Borrowed, Operand, Owned};
David Tolnay1e99fa32019-05-08 16:18:36 -07002use crate::{file, full, gen};
David Tolnay12597912019-05-08 17:02:44 -07003use proc_macro2::{Ident, Span, TokenStream};
David Tolnay4d918822019-05-08 17:05:52 -07004use quote::quote;
David Tolnay12597912019-05-08 17:02:44 -07005use syn::Index;
6use syn_codegen::{Data, Definitions, Features, Node, Type};
David Tolnay490b91b2019-05-08 15:46:09 -07007
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(
David Tolnay12597912019-05-08 17:02:44 -070033 ty: &Type,
34 features: &Features,
35 defs: &Definitions,
David Tolnay2fb3d482019-05-08 16:22:10 -070036 name: &Operand,
37) -> Option<TokenStream> {
38 match ty {
David Tolnay12597912019-05-08 17:02:44 -070039 Type::Box(t) => {
David Tolnaycd680542019-05-08 16:29:47 -070040 let name = name.owned_tokens();
41 visit(t, features, defs, &Owned(quote!(*#name)))
42 }
David Tolnay12597912019-05-08 17:02:44 -070043 Type::Vec(t) => {
David Tolnaycd680542019-05-08 16:29:47 -070044 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 }
David Tolnay12597912019-05-08 17:02:44 -070053 Type::Punctuated(p) => {
David Tolnaycd680542019-05-08 16:29:47 -070054 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 }
David Tolnay12597912019-05-08 17:02:44 -070064 Type::Option(t) => {
David Tolnaycd680542019-05-08 16:29:47 -070065 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 }
David Tolnay12597912019-05-08 17:02:44 -070074 Type::Tuple(t) => {
David Tolnaycd680542019-05-08 16:29:47 -070075 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));
David Tolnay4d918822019-05-08 17:05:52 -070081 code.extend(val);
82 code.extend(quote!(;));
David Tolnaycd680542019-05-08 16:29:47 -070083 }
84 Some(code)
85 }
David Tolnay12597912019-05-08 17:02:44 -070086 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 Tolnay12597912019-05-08 17:02:44 -070099 Type::Group(_) => {
David Tolnaycd680542019-05-08 16:29:47 -0700100 let name = name.tokens();
101 Some(quote! {
102 tokens_helper(_visitor, &#name.span)
103 })
104 }
David Tolnay12597912019-05-08 17:02:44 -0700105 Type::Syn(t) => {
106 fn requires_full(features: &Features) -> bool {
David Tolnay2fb3d482019-05-08 16:22:10 -0700107 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 }
David Tolnay12597912019-05-08 17:02:44 -0700116 Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)),
117 Type::Ext(_) | Type::Std(_) => None,
David Tolnay2fb3d482019-05-08 16:22:10 -0700118 }
119}
120
David Tolnay12597912019-05-08 17:02:44 -0700121fn visit_features(features: &Features) -> TokenStream {
David Tolnay2fb3d482019-05-08 16:22:10 -0700122 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
David Tolnay12597912019-05-08 17:02:44 -0700130fn node(state: &mut State, s: &Node, defs: &Definitions) {
David Tolnay2fb3d482019-05-08 16:22:10 -0700131 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 {
David Tolnay12597912019-05-08 17:02:44 -0700139 Data::Enum(variants) => {
David Tolnay2fb3d482019-05-08 16:22:10 -0700140 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() {
David Tolnay4d918822019-05-08 17:05:52 -0700146 visit_variants.extend(quote! {
David Tolnay2fb3d482019-05-08 16:22:10 -0700147 #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
David Tolnay4d918822019-05-08 17:05:52 -0700157 bind_visit_fields.extend(quote! {
David Tolnay2fb3d482019-05-08 16:22:10 -0700158 ref #binding,
159 });
160
161 let borrowed_binding = Borrowed(quote!(#binding));
162
David Tolnay4d918822019-05-08 17:05:52 -0700163 visit_fields.extend(
David Tolnay2fb3d482019-05-08 16:22:10 -0700164 visit(ty, &s.features, defs, &borrowed_binding)
165 .unwrap_or_else(|| noop_visit(&borrowed_binding)),
166 );
167
David Tolnay4d918822019-05-08 17:05:52 -0700168 visit_fields.extend(quote!(;));
David Tolnay2fb3d482019-05-08 16:22:10 -0700169 }
170
David Tolnay4d918822019-05-08 17:05:52 -0700171 visit_variants.extend(quote! {
David Tolnay2fb3d482019-05-08 16:22:10 -0700172 #ty::#variant_ident(#bind_visit_fields) => {
173 #visit_fields
174 }
175 });
176 }
177 }
178
David Tolnay4d918822019-05-08 17:05:52 -0700179 visit_impl.extend(quote! {
David Tolnay2fb3d482019-05-08 16:22:10 -0700180 match *_i {
181 #visit_variants
182 }
183 });
184 }
David Tolnay12597912019-05-08 17:02:44 -0700185 Data::Struct(fields) => {
David Tolnay2fb3d482019-05-08 16:22:10 -0700186 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));
David Tolnay4d918822019-05-08 17:05:52 -0700191 visit_impl.extend(quote! {
David Tolnay2fb3d482019-05-08 16:22:10 -0700192 #visit_field;
193 });
194 }
195 }
David Tolnay12597912019-05-08 17:02:44 -0700196 Data::Private => {}
David Tolnay2fb3d482019-05-08 16:22:10 -0700197 }
198
David Tolnay4d918822019-05-08 17:05:52 -0700199 state.visit_trait.extend(quote! {
David Tolnay2fb3d482019-05-08 16:22:10 -0700200 #features
201 fn #visit_fn(&mut self, i: &'ast #ty) {
202 #visit_fn(self, i)
203 }
204 });
205
David Tolnay4d918822019-05-08 17:05:52 -0700206 state.visit_impl.extend(quote! {
David Tolnay2fb3d482019-05-08 16:22:10 -0700207 #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 Tolnay12597912019-05-08 17:02:44 -0700216pub fn generate(defs: &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}