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 = el.value();
55 #val;
56 }
57 })
58 }
59 Type::Option(t) => {
60 let it = Borrowed(quote!(it));
61 let val = visit(t, features, defs, &it)?;
62 let name = name.ref_tokens();
63 Some(quote! {
64 if let Some(it) = #name {
65 #val;
66 }
67 })
68 }
69 Type::Tuple(t) => {
70 let mut code = TokenStream::new();
71 for (i, elem) in t.iter().enumerate() {
72 let name = name.tokens();
73 let i = Index::from(i);
74 let it = Owned(quote!((#name).#i));
75 let val = visit(elem, features, defs, &it).unwrap_or_else(|| noop_visit(&it));
76 code.extend(val);
77 code.extend(quote!(;));
78 }
79 Some(code)
80 }
81 Type::Syn(t) => {
82 fn requires_full(features: &Features) -> bool {
83 features.any.contains("full") && features.any.len() == 1
84 }
85 let mut res = simple_visit(t, name);
86 let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
87 if requires_full(&target.features) && !requires_full(features) {
88 res = quote!(full!(#res));
89 }
90 Some(res)
91 }
92 Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)),
93 Type::Ext(_) | Type::Std(_) | Type::Token(_) | Type::Group(_) => None,
94 }
95 }
96
node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Definitions)97 fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Definitions) {
98 let under_name = gen::under_name(&s.ident);
99 let ty = Ident::new(&s.ident, Span::call_site());
100 let visit_fn = format_ident!("visit_{}", under_name);
101
102 let mut visit_impl = TokenStream::new();
103
104 match &s.data {
105 Data::Enum(variants) if variants.is_empty() => {
106 visit_impl.extend(quote! {
107 match *node {}
108 });
109 }
110 Data::Enum(variants) => {
111 let mut visit_variants = TokenStream::new();
112
113 for (variant, fields) in variants {
114 let variant_ident = Ident::new(variant, Span::call_site());
115
116 if fields.is_empty() {
117 visit_variants.extend(quote! {
118 #ty::#variant_ident => {}
119 });
120 } else {
121 let mut bind_visit_fields = TokenStream::new();
122 let mut visit_fields = TokenStream::new();
123
124 for (idx, ty) in fields.iter().enumerate() {
125 let binding = format_ident!("_binding_{}", idx);
126
127 bind_visit_fields.extend(quote! {
128 #binding,
129 });
130
131 let borrowed_binding = Borrowed(quote!(#binding));
132
133 visit_fields.extend(
134 visit(ty, &s.features, defs, &borrowed_binding)
135 .unwrap_or_else(|| noop_visit(&borrowed_binding)),
136 );
137
138 visit_fields.extend(quote!(;));
139 }
140
141 visit_variants.extend(quote! {
142 #ty::#variant_ident(#bind_visit_fields) => {
143 #visit_fields
144 }
145 });
146 }
147 }
148
149 visit_impl.extend(quote! {
150 match node {
151 #visit_variants
152 }
153 });
154 }
155 Data::Struct(fields) => {
156 for (field, ty) in fields {
157 let id = Ident::new(field, Span::call_site());
158 let ref_toks = Owned(quote!(node.#id));
159 let visit_field = visit(ty, &s.features, defs, &ref_toks)
160 .unwrap_or_else(|| noop_visit(&ref_toks));
161 visit_impl.extend(quote! {
162 #visit_field;
163 });
164 }
165 }
166 Data::Private => {
167 if ty == "Ident" {
168 visit_impl.extend(quote! {
169 v.visit_span(&node.span());
170 });
171 }
172 }
173 }
174
175 let ast_lifetime = if s.ident == "Span" {
176 None
177 } else {
178 Some(quote!('ast))
179 };
180
181 traits.extend(quote! {
182 fn #visit_fn(&mut self, i: &#ast_lifetime #ty) {
183 #visit_fn(self, i);
184 }
185 });
186
187 impls.extend(quote! {
188 pub fn #visit_fn<'ast, V>(v: &mut V, node: &#ast_lifetime #ty)
189 where
190 V: Visit<'ast> + ?Sized,
191 {
192 #visit_impl
193 }
194 });
195 }
196
generate(defs: &Definitions) -> Result<()>197 pub fn generate(defs: &Definitions) -> Result<()> {
198 let (traits, impls) = gen::traverse(defs, node);
199 let full_macro = full::get_macro();
200 file::write(
201 VISIT_SRC,
202 quote! {
203 #![allow(unused_variables)]
204 #![allow(clippy::needless_pass_by_ref_mut)]
205
206 #[cfg(any(feature = "full", feature = "derive"))]
207 use crate::punctuated::Punctuated;
208 use crate::*;
209 use proc_macro2::Span;
210
211 #full_macro
212
213 macro_rules! skip {
214 ($($tt:tt)*) => {};
215 }
216
217 /// Syntax tree traversal to walk a shared borrow of a syntax tree.
218 ///
219 /// See the [module documentation] for details.
220 ///
221 /// [module documentation]: self
222 pub trait Visit<'ast> {
223 #traits
224 }
225
226 #impls
227 },
228 )?;
229 Ok(())
230 }
231