visit/
lib.rs

1#![recursion_limit = "128"]
2
3extern crate proc_macro;
4
5use quote::quote;
6
7mod codegen;
8mod parse;
9
10use syn::visit::Visit;
11
12/// Procedural macro to automatically generate code for the
13/// [Visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern)
14///
15/// # Example
16///
17/// ```
18/// use visit::visit;
19///
20/// visit! {
21///     #![visitor_trait = "Visitor"]
22///
23///     struct Bar {
24///         a: Child,
25///         b: Child,
26///     }
27///
28///     struct Child {}
29/// }
30///
31/// struct MyVisitor;
32///
33/// impl Visitor for MyVisitor {
34///     fn visit_child(&mut self, child: &Child) {
35///         // Do something cool
36///     }
37/// }
38///
39/// # fn main() {}
40/// ```
41///
42/// Use the `accept` method on the data structure you want to visit:
43///
44/// ```
45/// # use simple::*;
46/// #
47/// let mut visitor = MyVisitor {};
48/// let root = Bar { a: Child {}, b: Child {} };
49/// root.accept(&mut visitor);
50/// ```
51///
52/// # Attributes
53///
54/// ```ignore
55/// #![visitor_trait = "Visitor"]
56/// ```
57///
58/// Set the name of the visitor trait that should be generated.
59///
60/// ```ignore
61/// #![visitor_trait_pub = "Visitor"]
62/// ```
63///
64/// Like `#![visitor_trait]`, but the generated trait will be `pub`.
65///
66/// # Details
67///
68/// visit automatically generates a visitor trait named by the required `#![visitor_trait]` attribute:
69///
70/// ```
71/// # use simple::{Bar, Child};
72/// #
73/// trait Visitor {
74///     fn visit_bar(&mut self, bar: &Bar) {}
75///     fn visit_child(&mut self, child: &Child) {}
76///     // ...
77/// }
78/// ```
79///
80/// This trait specifies `visit` methods for all supported items (structs and enums) contained inside the `visit!` macro
81/// block.
82/// It also provides empty default implementations for all methods so you only need to implement the `visit_*` methods
83/// that are relevant to your current use case.
84///
85/// visit also generates an accept visitor trait. It is named `AcceptVisitor` where `Visitor` will be replaced by the
86/// name specified using the `#![visitor_trait]` attribute.
87///
88/// ```
89/// # use simple::Visitor;
90/// #
91/// trait AcceptVisitor {
92///     fn accept<V: Visitor>(&self, visitor: &mut V);
93/// }
94/// ```
95///
96/// This trait gets automatically implemented for all items contained inside the `visit!` macro block. For example, a
97/// trait implementation generated for `Bar` could look like this:
98///
99/// ```ignore
100/// impl AcceptVisitor for Bar {
101///     fn accept<V: Visitor>(&self, visitor: &mut V) {
102///         self.a.accept(visitor);
103///         self.b.accept(visitor);
104///         visitor.visit_bar(self);
105///     }
106/// }
107/// ```
108///
109/// visit also generates some default implementations for common collections and `Option<T>`. Primitive types are
110/// ignored (visit generates an empty accept trait implementation for them).
111///
112#[proc_macro]
113pub fn visit(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
114    let mut file: syn::File = syn::parse2(input.into()).unwrap();
115    // The only supported inner attribute in the visit macro context is a single `visitor_trait` declaration
116    let visitor_trait_info = parse::get_visitor_trait_info(&file);
117    let visitor_trait_ident = &visitor_trait_info.ident;
118    file.attrs = Vec::new();
119    let mut visitor = parse::ASTVisitor::new();
120    visitor.visit_file(&file);
121    let visitor_trait_gen =
122        codegen::generate_visitor_trait(&visitor_trait_info, &visitor.structs, &visitor.enums);
123    let accept_trait_ident = codegen::accept_visitor_trait_ident(&visitor_trait_ident);
124    let accept_trait_gen =
125        codegen::generate_accept_visitor_trait(&visitor_trait_info, &accept_trait_ident);
126    let accept_trait_impls =
127        codegen::generate_accept_visitor_impls(&visitor_trait_ident, &accept_trait_ident);
128
129    let mut accept_impls = proc_macro2::TokenStream::new();
130    for item_struct in visitor.structs {
131        let stream = codegen::generate_accept_impl_for_struct(
132            &visitor_trait_ident,
133            &accept_trait_ident,
134            &item_struct,
135        );
136        accept_impls.extend(stream);
137    }
138    for item_enum in visitor.enums {
139        let stream = codegen::generate_accept_impl_for_enum(
140            &visitor_trait_ident,
141            &accept_trait_ident,
142            &item_enum,
143        );
144        accept_impls.extend(stream);
145    }
146
147    let result = quote! {
148        #file
149        #visitor_trait_gen
150        #accept_trait_gen
151        #accept_trait_impls
152        #accept_impls
153    };
154    result.into()
155}