Split the visit and visit_mut codegen
diff --git a/codegen/src/visit.rs b/codegen/src/visit.rs
new file mode 100644
index 0000000..c13e95d
--- /dev/null
+++ b/codegen/src/visit.rs
@@ -0,0 +1,386 @@
+use crate::file;
+use quote::quote;
+use syn_codegen as types;
+
+const VISIT_SRC: &str = "../src/gen/visit.rs";
+
+mod codegen {
+ use inflections::Inflect;
+ use proc_macro2::{Span, TokenStream};
+ use quote::{quote, TokenStreamExt};
+ use syn::*;
+ use syn_codegen as types;
+
+ #[derive(Default)]
+ pub struct State {
+ pub visit_trait: TokenStream,
+ pub visit_impl: TokenStream,
+ }
+
+ fn under_name(name: &str) -> Ident {
+ Ident::new(&name.to_snake_case(), Span::call_site())
+ }
+
+ enum Operand {
+ Borrowed(TokenStream),
+ Owned(TokenStream),
+ }
+
+ use self::Operand::*;
+
+ impl Operand {
+ fn tokens(&self) -> &TokenStream {
+ match *self {
+ Borrowed(ref n) | Owned(ref n) => n,
+ }
+ }
+
+ fn ref_tokens(&self) -> TokenStream {
+ match *self {
+ Borrowed(ref n) => n.clone(),
+ Owned(ref n) => quote!(&#n),
+ }
+ }
+
+ fn owned_tokens(&self) -> TokenStream {
+ match *self {
+ Borrowed(ref n) => quote!(*#n),
+ Owned(ref n) => n.clone(),
+ }
+ }
+ }
+
+ fn simple_visit(item: &str, name: &Operand) -> TokenStream {
+ let ident = under_name(item);
+ let method = Ident::new(&format!("visit_{}", ident), Span::call_site());
+ let name = name.ref_tokens();
+ quote! {
+ _visitor.#method(#name)
+ }
+ }
+
+ fn box_visit(
+ elem: &types::Type,
+ features: &types::Features,
+ defs: &types::Definitions,
+ name: &Operand,
+ ) -> Option<TokenStream> {
+ let name = name.owned_tokens();
+ let res = visit(elem, features, defs, &Owned(quote!(*#name)))?;
+ Some(res)
+ }
+
+ fn vec_visit(
+ elem: &types::Type,
+ features: &types::Features,
+ defs: &types::Definitions,
+ name: &Operand,
+ ) -> Option<TokenStream> {
+ let operand = Borrowed(quote!(it));
+ let val = visit(elem, features, defs, &operand)?;
+ let name = name.ref_tokens();
+ Some(quote! {
+ for it in #name {
+ #val
+ }
+ })
+ }
+
+ fn punctuated_visit(
+ elem: &types::Type,
+ features: &types::Features,
+ defs: &types::Definitions,
+ name: &Operand,
+ ) -> Option<TokenStream> {
+ let operand = Borrowed(quote!(it));
+ let val = visit(elem, features, defs, &operand)?;
+ let name = name.ref_tokens();
+ Some(quote! {
+ for el in Punctuated::pairs(#name) {
+ let it = el.value();
+ #val
+ }
+ })
+ }
+
+ fn option_visit(
+ elem: &types::Type,
+ features: &types::Features,
+ defs: &types::Definitions,
+ name: &Operand,
+ ) -> Option<TokenStream> {
+ let it = Borrowed(quote!(it));
+ let val = visit(elem, features, defs, &it)?;
+ let name = name.owned_tokens();
+ Some(quote! {
+ if let Some(ref it) = #name {
+ #val
+ }
+ })
+ }
+
+ fn tuple_visit(
+ elems: &[types::Type],
+ features: &types::Features,
+ defs: &types::Definitions,
+ name: &Operand,
+ ) -> Option<TokenStream> {
+ if elems.is_empty() {
+ return None;
+ }
+
+ let mut code = TokenStream::new();
+ for (i, elem) in elems.iter().enumerate() {
+ let name = name.tokens();
+ let i = Index::from(i);
+ let it = Owned(quote!((#name).#i));
+ let val = visit(elem, features, defs, &it).unwrap_or_else(|| noop_visit(&it));
+ code.append_all(val);
+ code.append_all(quote!(;));
+ }
+ Some(code)
+ }
+
+ fn token_punct_visit(name: &Operand) -> TokenStream {
+ let name = name.tokens();
+ quote! {
+ tokens_helper(_visitor, &#name.spans)
+ }
+ }
+
+ fn token_keyword_visit(name: &Operand) -> TokenStream {
+ let name = name.tokens();
+ quote! {
+ tokens_helper(_visitor, &#name.span)
+ }
+ }
+
+ fn token_group_visit(name: &Operand) -> TokenStream {
+ let name = name.tokens();
+ quote! {
+ tokens_helper(_visitor, &#name.span)
+ }
+ }
+
+ fn noop_visit(name: &Operand) -> TokenStream {
+ let name = name.tokens();
+ quote! {
+ skip!(#name)
+ }
+ }
+
+ fn visit(
+ ty: &types::Type,
+ features: &types::Features,
+ defs: &types::Definitions,
+ name: &Operand,
+ ) -> Option<TokenStream> {
+ match ty {
+ types::Type::Box(t) => box_visit(&*t, features, defs, name),
+ types::Type::Vec(t) => vec_visit(&*t, features, defs, name),
+ types::Type::Punctuated(p) => punctuated_visit(&p.element, features, defs, name),
+ types::Type::Option(t) => option_visit(&*t, features, defs, name),
+ types::Type::Tuple(t) => tuple_visit(t, features, defs, name),
+ types::Type::Token(t) => {
+ let repr = &defs.tokens[t];
+ let is_keyword = repr.chars().next().unwrap().is_alphabetic();
+ if is_keyword {
+ Some(token_keyword_visit(name))
+ } else {
+ Some(token_punct_visit(name))
+ }
+ }
+ types::Type::Group(_) => Some(token_group_visit(name)),
+ types::Type::Syn(t) => {
+ fn requires_full(features: &types::Features) -> bool {
+ features.any.contains("full") && features.any.len() == 1
+ }
+
+ let res = simple_visit(t, name);
+
+ let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap();
+
+ Some(
+ if requires_full(&target.features) && !requires_full(features) {
+ quote! {
+ full!(#res)
+ }
+ } else {
+ res
+ },
+ )
+ }
+ types::Type::Ext(t) if super::TERMINAL_TYPES.contains(&&t[..]) => {
+ Some(simple_visit(t, name))
+ }
+ types::Type::Ext(_) | types::Type::Std(_) => None,
+ }
+ }
+
+ fn visit_features(features: &types::Features) -> TokenStream {
+ let features = &features.any;
+ match features.len() {
+ 0 => quote!(),
+ 1 => quote!(#[cfg(feature = #(#features)*)]),
+ _ => quote!(#[cfg(any(#(feature = #features),*))]),
+ }
+ }
+
+ pub fn generate(state: &mut State, s: &types::Node, defs: &types::Definitions) {
+ let features = visit_features(&s.features);
+ let under_name = under_name(&s.ident);
+ let ty = Ident::new(&s.ident, Span::call_site());
+ let visit_fn = Ident::new(&format!("visit_{}", under_name), Span::call_site());
+
+ let mut visit_impl = TokenStream::new();
+
+ match &s.data {
+ types::Data::Enum(variants) => {
+ let mut visit_variants = TokenStream::new();
+
+ for (variant, fields) in variants {
+ let variant_ident = Ident::new(variant, Span::call_site());
+
+ if fields.is_empty() {
+ visit_variants.append_all(quote! {
+ #ty::#variant_ident => {}
+ });
+ } else {
+ let mut bind_visit_fields = TokenStream::new();
+ let mut visit_fields = TokenStream::new();
+
+ for (idx, ty) in fields.iter().enumerate() {
+ let name = format!("_binding_{}", idx);
+ let binding = Ident::new(&name, Span::call_site());
+
+ bind_visit_fields.append_all(quote! {
+ ref #binding,
+ });
+
+ let borrowed_binding = Borrowed(quote!(#binding));
+
+ visit_fields.append_all(
+ visit(ty, &s.features, defs, &borrowed_binding)
+ .unwrap_or_else(|| noop_visit(&borrowed_binding)),
+ );
+
+ visit_fields.append_all(quote!(;));
+ }
+
+ visit_variants.append_all(quote! {
+ #ty::#variant_ident(#bind_visit_fields) => {
+ #visit_fields
+ }
+ });
+ }
+ }
+
+ visit_impl.append_all(quote! {
+ match *_i {
+ #visit_variants
+ }
+ });
+ }
+ types::Data::Struct(fields) => {
+ for (field, ty) in fields {
+ let id = Ident::new(&field, Span::call_site());
+ let ref_toks = Owned(quote!(_i.#id));
+ let visit_field = visit(&ty, &s.features, defs, &ref_toks)
+ .unwrap_or_else(|| noop_visit(&ref_toks));
+ visit_impl.append_all(quote! {
+ #visit_field;
+ });
+ }
+ }
+ types::Data::Private => {}
+ }
+
+ state.visit_trait.append_all(quote! {
+ #features
+ fn #visit_fn(&mut self, i: &'ast #ty) {
+ #visit_fn(self, i)
+ }
+ });
+
+ state.visit_impl.append_all(quote! {
+ #features
+ pub fn #visit_fn<'ast, V: Visit<'ast> + ?Sized>(
+ _visitor: &mut V, _i: &'ast #ty
+ ) {
+ #visit_impl
+ }
+ });
+ }
+}
+
+const TERMINAL_TYPES: &[&str] = &["Span", "Ident"];
+
+pub fn generate(defs: &types::Definitions) {
+ let mut state = codegen::State::default();
+ for s in &defs.types {
+ codegen::generate(&mut state, s, defs);
+ }
+ for tt in TERMINAL_TYPES {
+ let s = types::Node {
+ ident: tt.to_string(),
+ features: types::Features::default(),
+ data: types::Data::Private,
+ };
+ codegen::generate(&mut state, &s, defs);
+ }
+
+ let full_macro = quote! {
+ #[cfg(feature = "full")]
+ macro_rules! full {
+ ($e:expr) => {
+ $e
+ };
+ }
+
+ #[cfg(all(feature = "derive", not(feature = "full")))]
+ macro_rules! full {
+ ($e:expr) => {
+ unreachable!()
+ };
+ }
+ };
+
+ let skip_macro = quote! {
+ #[cfg(any(feature = "full", feature = "derive"))]
+ macro_rules! skip {
+ ($($tt:tt)*) => {};
+ }
+ };
+
+ let visit_trait = state.visit_trait;
+ let visit_impl = state.visit_impl;
+ file::write(
+ VISIT_SRC,
+ quote! {
+ #![cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))]
+
+ use *;
+ #[cfg(any(feature = "full", feature = "derive"))]
+ use punctuated::Punctuated;
+ use proc_macro2::Span;
+ #[cfg(any(feature = "full", feature = "derive"))]
+ use gen::helper::visit::*;
+
+ #full_macro
+ #skip_macro
+
+ /// Syntax tree traversal to walk a shared borrow of a syntax tree.
+ ///
+ /// See the [module documentation] for details.
+ ///
+ /// [module documentation]: index.html
+ ///
+ /// *This trait is available if Syn is built with the `"visit"` feature.*
+ pub trait Visit<'ast> {
+ #visit_trait
+ }
+
+ #visit_impl
+ },
+ );
+}