xref: /third_party/rust/crates/syn/codegen/src/visit.rs (revision fad3a1d3)
1use crate::operand::{Borrowed, Operand, Owned};
2use crate::{file, full, gen};
3use anyhow::Result;
4use proc_macro2::{Ident, Span, TokenStream};
5use quote::{format_ident, quote};
6use syn::Index;
7use syn_codegen::{Data, Definitions, Features, Node, Type};
8
9const VISIT_SRC: &str = "src/gen/visit.rs";
10
11fn 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
20fn noop_visit(name: &Operand) -> TokenStream {
21    let name = name.tokens();
22    quote! {
23        skip!(#name)
24    }
25}
26
27fn 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
97fn 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
197pub 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