• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::operand::{Borrowed, Operand, Owned};
2 use crate::{file, full, gen};
3 use anyhow::Result;
4 use proc_macro2::{Ident, Span, TokenStream};
5 use quote::{format_ident, quote};
6 use syn::Index;
7 use syn_codegen::{Data, Definitions, Features, Node, Type};
8 
9 const VISIT_SRC: &str = "../src/gen/visit.rs";
10 
simple_visit(item: &str, name: &Operand) -> TokenStream11 fn simple_visit(item: &str, name: &Operand) -> TokenStream {
12     let ident = gen::under_name(item);
13     let method = format_ident!("visit_{}", ident);
14     let name = name.ref_tokens();
15     quote! {
16         v.#method(#name)
17     }
18 }
19 
noop_visit(name: &Operand) -> TokenStream20 fn noop_visit(name: &Operand) -> TokenStream {
21     let name = name.tokens();
22     quote! {
23         skip!(#name)
24     }
25 }
26 
visit( ty: &Type, features: &Features, defs: &Definitions, name: &Operand, ) -> Option<TokenStream>27 fn visit(
28     ty: &Type,
29     features: &Features,
30     defs: &Definitions,
31     name: &Operand,
32 ) -> Option<TokenStream> {
33     match ty {
34         Type::Box(t) => {
35             let name = name.owned_tokens();
36             visit(t, features, defs, &Owned(quote!(*#name)))
37         }
38         Type::Vec(t) => {
39             let operand = Borrowed(quote!(it));
40             let val = visit(t, features, defs, &operand)?;
41             let name = name.ref_tokens();
42             Some(quote! {
43                 for it in #name {
44                     #val;
45                 }
46             })
47         }
48         Type::Punctuated(p) => {
49             let operand = Borrowed(quote!(it));
50             let val = visit(&p.element, features, defs, &operand)?;
51             let name = name.ref_tokens();
52             Some(quote! {
53                 for el in Punctuated::pairs(#name) {
54                     let (it, p) = el.into_tuple();
55                     #val;
56                     if let Some(p) = p {
57                         tokens_helper(v, &p.spans);
58                     }
59                 }
60             })
61         }
62         Type::Option(t) => {
63             let it = Borrowed(quote!(it));
64             let val = visit(t, features, defs, &it)?;
65             let name = name.owned_tokens();
66             Some(quote! {
67                 if let Some(it) = &#name {
68                     #val;
69                 }
70             })
71         }
72         Type::Tuple(t) => {
73             let mut code = TokenStream::new();
74             for (i, elem) in t.iter().enumerate() {
75                 let name = name.tokens();
76                 let i = Index::from(i);
77                 let it = Owned(quote!((#name).#i));
78                 let val = visit(elem, features, defs, &it).unwrap_or_else(|| noop_visit(&it));
79                 code.extend(val);
80                 code.extend(quote!(;));
81             }
82             Some(code)
83         }
84         Type::Token(t) => {
85             let name = name.tokens();
86             let repr = &defs.tokens[t];
87             let is_keyword = repr.chars().next().unwrap().is_alphabetic();
88             let spans = if is_keyword {
89                 quote!(span)
90             } else {
91                 quote!(spans)
92             };
93             Some(quote! {
94                 tokens_helper(v, &#name.#spans);
95             })
96         }
97         Type::Group(_) => {
98             let name = name.tokens();
99             Some(quote! {
100                 tokens_helper(v, &#name.span);
101             })
102         }
103         Type::Syn(t) => {
104             fn requires_full(features: &Features) -> bool {
105                 features.any.contains("full") && features.any.len() == 1
106             }
107             let mut res = simple_visit(t, name);
108             let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
109             if requires_full(&target.features) && !requires_full(features) {
110                 res = quote!(full!(#res));
111             }
112             Some(res)
113         }
114         Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)),
115         Type::Ext(_) | Type::Std(_) => None,
116     }
117 }
118 
node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Definitions)119 fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Definitions) {
120     let under_name = gen::under_name(&s.ident);
121     let ty = Ident::new(&s.ident, Span::call_site());
122     let visit_fn = format_ident!("visit_{}", under_name);
123 
124     let mut visit_impl = TokenStream::new();
125 
126     match &s.data {
127         Data::Enum(variants) => {
128             let mut visit_variants = TokenStream::new();
129 
130             for (variant, fields) in variants {
131                 let variant_ident = Ident::new(variant, Span::call_site());
132 
133                 if fields.is_empty() {
134                     visit_variants.extend(quote! {
135                         #ty::#variant_ident => {}
136                     });
137                 } else {
138                     let mut bind_visit_fields = TokenStream::new();
139                     let mut visit_fields = TokenStream::new();
140 
141                     for (idx, ty) in fields.iter().enumerate() {
142                         let binding = format_ident!("_binding_{}", idx);
143 
144                         bind_visit_fields.extend(quote! {
145                             #binding,
146                         });
147 
148                         let borrowed_binding = Borrowed(quote!(#binding));
149 
150                         visit_fields.extend(
151                             visit(ty, &s.features, defs, &borrowed_binding)
152                                 .unwrap_or_else(|| noop_visit(&borrowed_binding)),
153                         );
154 
155                         visit_fields.extend(quote!(;));
156                     }
157 
158                     visit_variants.extend(quote! {
159                         #ty::#variant_ident(#bind_visit_fields) => {
160                             #visit_fields
161                         }
162                     });
163                 }
164             }
165 
166             let nonexhaustive = if s.exhaustive {
167                 None
168             } else {
169                 Some(quote! {
170                     #[cfg(syn_no_non_exhaustive)]
171                     _ => unreachable!(),
172                 })
173             };
174 
175             visit_impl.extend(quote! {
176                 match node {
177                     #visit_variants
178                     #nonexhaustive
179                 }
180             });
181         }
182         Data::Struct(fields) => {
183             for (field, ty) in fields {
184                 if let Type::Syn(ty) = ty {
185                     if ty == "Reserved" {
186                         continue;
187                     }
188                 }
189 
190                 let id = Ident::new(&field, Span::call_site());
191                 let ref_toks = Owned(quote!(node.#id));
192                 let visit_field = visit(&ty, &s.features, defs, &ref_toks)
193                     .unwrap_or_else(|| noop_visit(&ref_toks));
194                 visit_impl.extend(quote! {
195                     #visit_field;
196                 });
197             }
198         }
199         Data::Private => {
200             if ty == "Ident" {
201                 visit_impl.extend(quote! {
202                     v.visit_span(&node.span());
203                 });
204             }
205         }
206     }
207 
208     let ast_lifetime = if s.ident == "Span" {
209         None
210     } else {
211         Some(quote!('ast))
212     };
213 
214     traits.extend(quote! {
215         fn #visit_fn(&mut self, i: &#ast_lifetime #ty) {
216             #visit_fn(self, i);
217         }
218     });
219 
220     impls.extend(quote! {
221         pub fn #visit_fn<'ast, V>(v: &mut V, node: &#ast_lifetime #ty)
222         where
223             V: Visit<'ast> + ?Sized,
224         {
225             #visit_impl
226         }
227     });
228 }
229 
generate(defs: &Definitions) -> Result<()>230 pub fn generate(defs: &Definitions) -> Result<()> {
231     let (traits, impls) = gen::traverse(defs, node);
232     let full_macro = full::get_macro();
233     file::write(
234         VISIT_SRC,
235         quote! {
236             #![allow(unused_variables)]
237 
238             #[cfg(any(feature = "full", feature = "derive"))]
239             use crate::gen::helper::visit::*;
240             #[cfg(any(feature = "full", feature = "derive"))]
241             use crate::punctuated::Punctuated;
242             use crate::*;
243             use proc_macro2::Span;
244 
245             #full_macro
246 
247             macro_rules! skip {
248                 ($($tt:tt)*) => {};
249             }
250 
251             /// Syntax tree traversal to walk a shared borrow of a syntax tree.
252             ///
253             /// See the [module documentation] for details.
254             ///
255             /// [module documentation]: self
256             ///
257             /// *This trait is available only if Syn is built with the `"visit"` feature.*
258             pub trait Visit<'ast> {
259                 #traits
260             }
261 
262             #impls
263         },
264     )?;
265     Ok(())
266 }
267