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}