blob: 9a12cee3c04afae1b0cc66c990026c4ee7e86867 [file] [log] [blame]
David Tolnay4b4c4b62018-01-06 13:48:05 -08001//! This crate automatically generates the definition of the `Visit`,
2//! `VisitMut`, and `Fold` traits in `syn` based on the `syn` source. It
Nika Layzell27726662017-10-24 23:16:35 -04003//! discovers structs and enums declared with the `ast_*` macros and generates
4//! the functions for those types.
5//!
6//! It makes a few assumptions about the target crate:
7//! 1. All structs which are discovered must be re-exported in the root of the
8//! crate, even if they were declared in a submodule.
9//! 2. This code cannot discover submodules which are located in subdirectories
10//! - only submodules located in the same directory.
11//! 3. The path to `syn` is hardcoded.
12
David Tolnay6af48992018-08-01 11:16:28 -070013#![recursion_limit = "128"]
David Tolnay6b46a702018-08-01 23:51:32 -070014#![cfg_attr(
15 feature = "cargo-clippy",
16 allow(
17 needless_pass_by_value,
18 redundant_closure,
19 write_with_newline,
20 )
21)]
David Tolnayea9ae892017-12-26 01:44:32 -050022
David Tolnayd67fb752017-12-27 13:50:29 -050023#[macro_use]
24extern crate failure;
Nika Layzell27726662017-10-24 23:16:35 -040025extern crate inflections;
David Tolnay2e0dba12017-12-27 01:54:40 -050026extern crate proc_macro2;
David Tolnayd67fb752017-12-27 13:50:29 -050027#[macro_use]
28extern crate quote;
David Tolnay5c4c0b52017-12-28 17:58:54 -050029#[macro_use]
David Tolnayd67fb752017-12-27 13:50:29 -050030extern crate syn;
David Tolnay8c81f622018-07-31 23:34:35 -070031extern crate rustfmt_nightly as rustfmt;
Nika Layzell27726662017-10-24 23:16:35 -040032
David Tolnayd67fb752017-12-27 13:50:29 -050033use failure::{err_msg, Error};
David Tolnaye303b7c2018-05-20 16:46:35 -070034use proc_macro2::{Span, TokenStream};
David Tolnay01ed0ce2018-05-20 20:18:14 -070035use quote::ToTokens;
36use syn::{Attribute, Data, DataStruct, DeriveInput, Ident, Item};
Nika Layzell27726662017-10-24 23:16:35 -040037
David Tolnay01ed0ce2018-05-20 20:18:14 -070038use std::collections::BTreeMap;
David Tolnayf0d63bf2017-12-26 12:29:47 -050039use std::fmt::{self, Debug};
Nika Layzell27726662017-10-24 23:16:35 -040040use std::fs::File;
David Tolnay6af48992018-08-01 11:16:28 -070041use std::io::{Read, Write};
Nika Layzell27726662017-10-24 23:16:35 -040042use std::path::Path;
Nika Layzell27726662017-10-24 23:16:35 -040043
44const SYN_CRATE_ROOT: &str = "../src/lib.rs";
45
46const FOLD_SRC: &str = "../src/gen/fold.rs";
47const VISIT_SRC: &str = "../src/gen/visit.rs";
48const VISIT_MUT_SRC: &str = "../src/gen/visit_mut.rs";
49
David Tolnayd67fb752017-12-27 13:50:29 -050050const IGNORED_MODS: &[&str] = &["fold", "visit", "visit_mut"];
Nika Layzell27726662017-10-24 23:16:35 -040051
Alex Crichton131308c2018-05-18 14:00:24 -070052const EXTRA_TYPES: &[&str] = &["Lifetime"];
David Tolnay4ba63a02017-12-28 15:53:05 -050053
Alex Crichtond261d092018-05-18 13:47:35 -070054const TERMINAL_TYPES: &[&str] = &["Span", "Ident"];
Nika Layzellefb83ba2017-12-19 18:23:55 -050055
Alex Crichtona74a1c82018-05-16 10:20:44 -070056fn path_eq(a: &syn::Path, b: &str) -> bool {
57 if a.global() {
David Tolnay01ed0ce2018-05-20 20:18:14 -070058 return false;
Nika Layzell27726662017-10-24 23:16:35 -040059 }
Alex Crichtona74a1c82018-05-16 10:20:44 -070060 if a.segments.len() != 1 {
David Tolnay01ed0ce2018-05-20 20:18:14 -070061 return false;
Alex Crichtona74a1c82018-05-16 10:20:44 -070062 }
David Tolnay446f7d62018-05-20 17:58:15 -070063 a.segments[0].ident == b
Nika Layzell27726662017-10-24 23:16:35 -040064}
65
Alex Crichton715862b2018-05-17 12:31:49 -070066fn get_features(attrs: &[Attribute], mut features: TokenStream) -> TokenStream {
Nika Layzell27726662017-10-24 23:16:35 -040067 for attr in attrs {
Alex Crichtona74a1c82018-05-16 10:20:44 -070068 if path_eq(&attr.path, "cfg") {
Nika Layzell27726662017-10-24 23:16:35 -040069 attr.to_tokens(&mut features);
70 }
71 }
72 features
73}
74
75#[derive(Clone)]
76pub struct AstItem {
David Tolnay3d772182017-12-28 17:18:53 -050077 ast: DeriveInput,
Alex Crichton715862b2018-05-17 12:31:49 -070078 features: TokenStream,
Nika Layzell27726662017-10-24 23:16:35 -040079 // True if this is an ast_enum_of_structs! item with a #full annotation.
80 eos_full: bool,
81}
82
David Tolnayf0d63bf2017-12-26 12:29:47 -050083impl Debug for AstItem {
Nika Layzell27726662017-10-24 23:16:35 -040084 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85 f.debug_struct("AstItem")
David Tolnay3d772182017-12-28 17:18:53 -050086 .field("ast", &self.ast)
Nika Layzell27726662017-10-24 23:16:35 -040087 .field("features", &self.features.to_string())
88 .finish()
89 }
90}
91
92// NOTE: BTreeMap is used here instead of HashMap to have deterministic output.
David Tolnayc47f8422017-12-28 17:30:46 -050093type Lookup = BTreeMap<Ident, AstItem>;
Nika Layzell27726662017-10-24 23:16:35 -040094
David Tolnay01ed0ce2018-05-20 20:18:14 -070095fn load_file<P: AsRef<Path>>(
96 name: P,
97 features: &TokenStream,
98 lookup: &mut Lookup,
99) -> Result<(), Error> {
Nika Layzell27726662017-10-24 23:16:35 -0400100 let name = name.as_ref();
David Tolnayea9ae892017-12-26 01:44:32 -0500101 let parent = name.parent().ok_or_else(|| err_msg("no parent path"))?;
Nika Layzell27726662017-10-24 23:16:35 -0400102
103 let mut f = File::open(name)?;
104 let mut src = String::new();
105 f.read_to_string(&mut src)?;
106
107 // Parse the file
David Tolnayd67fb752017-12-27 13:50:29 -0500108 let file =
109 syn::parse_file(&src).map_err(|_| format_err!("failed to parse {}", name.display()))?;
Nika Layzell27726662017-10-24 23:16:35 -0400110
111 // Collect all of the interesting AstItems declared in this file or submodules.
David Tolnay4ba63a02017-12-28 15:53:05 -0500112 'items: for item in file.items {
113 match item {
114 Item::Mod(item) => {
Nika Layzell27726662017-10-24 23:16:35 -0400115 // Don't inspect inline modules.
David Tolnayc6b55bc2017-11-09 22:48:38 -0800116 if item.content.is_some() {
Nika Layzell27726662017-10-24 23:16:35 -0400117 continue;
118 }
119
120 // We don't want to try to load the generated rust files and
121 // parse them, so we ignore them here.
122 for name in IGNORED_MODS {
David Tolnay446f7d62018-05-20 17:58:15 -0700123 if item.ident == name {
Nika Layzell27726662017-10-24 23:16:35 -0400124 continue 'items;
125 }
126 }
127
128 // Lookup any #[cfg()] attributes on the module and add them to
129 // the feature set.
David Tolnay0a0d78c2018-01-05 15:24:01 -0800130 //
131 // The derive module is weird because it is built with either
132 // `full` or `derive` but exported only under `derive`.
David Tolnay446f7d62018-05-20 17:58:15 -0700133 let features = if item.ident == "derive" {
David Tolnay0a0d78c2018-01-05 15:24:01 -0800134 quote!(#[cfg(feature = "derive")])
135 } else {
136 get_features(&item.attrs, features.clone())
137 };
Nika Layzell27726662017-10-24 23:16:35 -0400138
139 // Look up the submodule file, and recursively parse it.
140 // XXX: Only handles same-directory .rs file submodules.
Alex Crichtona74a1c82018-05-16 10:20:44 -0700141 let path = parent.join(&format!("{}.rs", item.ident));
David Tolnayea9ae892017-12-26 01:44:32 -0500142 load_file(path, &features, lookup)?;
Nika Layzell27726662017-10-24 23:16:35 -0400143 }
David Tolnay4ba63a02017-12-28 15:53:05 -0500144 Item::Macro(item) => {
Nika Layzell27726662017-10-24 23:16:35 -0400145 // Lookip any #[cfg()] attributes directly on the macro
146 // invocation, and add them to the feature set.
147 let features = get_features(&item.attrs, features.clone());
148
149 // Try to parse the AstItem declaration out of the item.
David Tolnay01a77582018-01-01 20:00:51 -0800150 let tts = &item.mac.tts;
Alex Crichtona74a1c82018-05-16 10:20:44 -0700151 let found = if path_eq(&item.mac.path, "ast_struct") {
David Tolnay01a77582018-01-01 20:00:51 -0800152 syn::parse_str::<parsing::AstStruct>(&quote!(#tts).to_string())
David Tolnayd67fb752017-12-27 13:50:29 -0500153 .map_err(|_| err_msg("failed to parse ast_struct"))?
154 .0
Alex Crichtona74a1c82018-05-16 10:20:44 -0700155 } else if path_eq(&item.mac.path, "ast_enum") {
David Tolnay01a77582018-01-01 20:00:51 -0800156 syn::parse_str::<parsing::AstEnum>(&quote!(#tts).to_string())
David Tolnayd67fb752017-12-27 13:50:29 -0500157 .map_err(|_| err_msg("failed to parse ast_enum"))?
158 .0
Alex Crichtona74a1c82018-05-16 10:20:44 -0700159 } else if path_eq(&item.mac.path, "ast_enum_of_structs") {
David Tolnay01a77582018-01-01 20:00:51 -0800160 syn::parse_str::<parsing::AstEnumOfStructs>(&quote!(#tts).to_string())
David Tolnay1cf80912017-12-31 18:35:12 -0500161 .map_err(|_| err_msg("failed to parse ast_enum_of_structs"))?
David Tolnayd67fb752017-12-27 13:50:29 -0500162 .0
Nika Layzell27726662017-10-24 23:16:35 -0400163 } else {
David Tolnayd67fb752017-12-27 13:50:29 -0500164 continue;
Nika Layzell27726662017-10-24 23:16:35 -0400165 };
166
167 // Record our features on the parsed AstItems.
168 for mut item in found {
169 features.to_tokens(&mut item.features);
Alex Crichtona74a1c82018-05-16 10:20:44 -0700170 lookup.insert(item.ast.ident.clone(), item);
Nika Layzell27726662017-10-24 23:16:35 -0400171 }
172 }
David Tolnay4ba63a02017-12-28 15:53:05 -0500173 Item::Struct(item) => {
174 let ident = item.ident;
Alex Crichtona74a1c82018-05-16 10:20:44 -0700175 if EXTRA_TYPES.contains(&&ident.to_string()[..]) {
David Tolnay01ed0ce2018-05-20 20:18:14 -0700176 lookup.insert(
177 ident.clone(),
178 AstItem {
179 ast: DeriveInput {
David Tolnay6b46a702018-08-01 23:51:32 -0700180 ident,
David Tolnay01ed0ce2018-05-20 20:18:14 -0700181 vis: item.vis,
182 attrs: item.attrs,
183 generics: item.generics,
184 data: Data::Struct(DataStruct {
185 fields: item.fields,
186 struct_token: item.struct_token,
187 semi_token: item.semi_token,
188 }),
189 },
190 features: features.clone(),
191 eos_full: false,
David Tolnay4ba63a02017-12-28 15:53:05 -0500192 },
David Tolnay01ed0ce2018-05-20 20:18:14 -0700193 );
David Tolnay4ba63a02017-12-28 15:53:05 -0500194 }
195 }
Nika Layzell27726662017-10-24 23:16:35 -0400196 _ => {}
197 }
198 }
199 Ok(())
200}
201
202mod parsing {
203 use super::AstItem;
204
David Tolnay01ed0ce2018-05-20 20:18:14 -0700205 use proc_macro2::TokenStream;
David Tolnay1cf80912017-12-31 18:35:12 -0500206 use syn;
David Tolnayc5ab8c62017-12-26 16:43:39 -0500207 use syn::synom::*;
Nika Layzell27726662017-10-24 23:16:35 -0400208 use syn::*;
Nika Layzell27726662017-10-24 23:16:35 -0400209
210 // Parses #full - returns #[cfg(feature = "full")] if it is present, and
211 // nothing otherwise.
Alex Crichton715862b2018-05-17 12:31:49 -0700212 named!(full -> (TokenStream, bool), map!(option!(do_parse!(
David Tolnayf8db7ba2017-11-11 22:52:16 -0800213 punct!(#) >>
Nika Layzell27726662017-10-24 23:16:35 -0400214 id: syn!(Ident) >>
David Tolnay446f7d62018-05-20 17:58:15 -0700215 cond_reduce!(id == "full") >>
David Tolnayea9ae892017-12-26 01:44:32 -0500216 ()
Nika Layzell27726662017-10-24 23:16:35 -0400217 )), |s| if s.is_some() {
218 (quote!(#[cfg(feature = "full")]), true)
219 } else {
220 (quote!(), false)
221 }));
222
David Tolnay28c5a462017-12-27 01:59:30 -0500223 named!(manual_extra_traits -> (), do_parse!(
224 punct!(#) >>
225 id: syn!(Ident) >>
David Tolnay446f7d62018-05-20 17:58:15 -0700226 cond_reduce!(id == "manual_extra_traits") >>
David Tolnay28c5a462017-12-27 01:59:30 -0500227 ()
228 ));
229
Nika Layzell27726662017-10-24 23:16:35 -0400230 // Parses a simple AstStruct without the `pub struct` prefix.
231 named!(ast_struct_inner -> AstItem, do_parse!(
232 id: syn!(Ident) >>
233 features: full >>
David Tolnay28c5a462017-12-27 01:59:30 -0500234 option!(manual_extra_traits) >>
David Tolnay2e0dba12017-12-27 01:54:40 -0500235 rest: syn!(TokenStream) >>
Nika Layzell27726662017-10-24 23:16:35 -0400236 (AstItem {
David Tolnay01a77582018-01-01 20:00:51 -0800237 ast: syn::parse_str(&quote! {
David Tolnay2e0dba12017-12-27 01:54:40 -0500238 pub struct #id #rest
David Tolnay01a77582018-01-01 20:00:51 -0800239 }.to_string())?,
Nika Layzell27726662017-10-24 23:16:35 -0400240 features: features.0,
241 eos_full: features.1,
242 })
243 ));
244
245 // ast_struct! parsing
246 pub struct AstStruct(pub Vec<AstItem>);
247 impl Synom for AstStruct {
David Tolnayab919512017-12-30 23:31:51 -0500248 named!(parse -> Self, do_parse!(
David Tolnay46f0a4c2018-08-26 08:48:52 -0400249 many0!(Attribute::old_parse_outer) >>
David Tolnayf8db7ba2017-11-11 22:52:16 -0800250 keyword!(pub) >>
251 keyword!(struct) >>
Nika Layzell27726662017-10-24 23:16:35 -0400252 res: call!(ast_struct_inner) >>
David Tolnayab919512017-12-30 23:31:51 -0500253 (AstStruct(vec![res]))
254 ));
Nika Layzell27726662017-10-24 23:16:35 -0400255 }
256
David Tolnay360efd22018-01-04 23:35:26 -0800257 named!(no_visit -> (), do_parse!(
258 punct!(#) >>
259 id: syn!(Ident) >>
David Tolnay446f7d62018-05-20 17:58:15 -0700260 cond_reduce!(id == "no_visit") >>
David Tolnay360efd22018-01-04 23:35:26 -0800261 ()
262 ));
263
Nika Layzell27726662017-10-24 23:16:35 -0400264 // ast_enum! parsing
265 pub struct AstEnum(pub Vec<AstItem>);
266 impl Synom for AstEnum {
David Tolnay360efd22018-01-04 23:35:26 -0800267 named!(parse -> Self, do_parse!(
David Tolnay46f0a4c2018-08-26 08:48:52 -0400268 many0!(Attribute::old_parse_outer) >>
David Tolnay360efd22018-01-04 23:35:26 -0800269 keyword!(pub) >>
270 keyword!(enum) >>
271 id: syn!(Ident) >>
272 no_visit: option!(no_visit) >>
273 rest: syn!(TokenStream) >>
274 (AstEnum(if no_visit.is_some() {
275 vec![]
276 } else {
277 vec![AstItem {
278 ast: syn::parse_str(&quote! {
279 pub enum #id #rest
280 }.to_string())?,
281 features: quote!(),
282 eos_full: false,
283 }]
284 }))
285 ));
Nika Layzell27726662017-10-24 23:16:35 -0400286 }
287
288 // A single variant of an ast_enum_of_structs!
289 struct EosVariant {
290 name: Ident,
David Tolnayfcfb9002017-12-28 22:04:29 -0500291 member: Option<Path>,
Nika Layzell27726662017-10-24 23:16:35 -0400292 inner: Option<AstItem>,
293 }
294 named!(eos_variant -> EosVariant, do_parse!(
David Tolnay46f0a4c2018-08-26 08:48:52 -0400295 many0!(Attribute::old_parse_outer) >>
David Tolnayf8db7ba2017-11-11 22:52:16 -0800296 keyword!(pub) >>
Nika Layzell27726662017-10-24 23:16:35 -0400297 variant: syn!(Ident) >>
David Tolnayfcfb9002017-12-28 22:04:29 -0500298 member: option!(map!(parens!(alt!(
Alex Crichtona74a1c82018-05-16 10:20:44 -0700299 call!(ast_struct_inner) => { |x: AstItem| (Path::from(x.ast.ident.clone()), Some(x)) }
Nika Layzell27726662017-10-24 23:16:35 -0400300 |
301 syn!(Path) => { |x| (x, None) }
David Tolnay8875fca2017-12-31 13:52:37 -0500302 )), |x| x.1)) >>
David Tolnayf8db7ba2017-11-11 22:52:16 -0800303 punct!(,) >>
Nika Layzell27726662017-10-24 23:16:35 -0400304 (EosVariant {
305 name: variant,
David Tolnayfcfb9002017-12-28 22:04:29 -0500306 member: member.clone().map(|x| x.0),
307 inner: member.map(|x| x.1).unwrap_or_default(),
Nika Layzell27726662017-10-24 23:16:35 -0400308 })
309 ));
310
311 // ast_enum_of_structs! parsing
312 pub struct AstEnumOfStructs(pub Vec<AstItem>);
313 impl Synom for AstEnumOfStructs {
David Tolnayab919512017-12-30 23:31:51 -0500314 named!(parse -> Self, do_parse!(
David Tolnay46f0a4c2018-08-26 08:48:52 -0400315 many0!(Attribute::old_parse_outer) >>
David Tolnayf8db7ba2017-11-11 22:52:16 -0800316 keyword!(pub) >>
317 keyword!(enum) >>
Nika Layzell27726662017-10-24 23:16:35 -0400318 id: syn!(Ident) >>
David Tolnaye3d41b72017-12-31 15:24:00 -0500319 variants: braces!(many0!(eos_variant)) >>
Nika Layzell27726662017-10-24 23:16:35 -0400320 option!(syn!(Ident)) >> // do_not_generate_to_tokens
321 ({
Nika Layzell27726662017-10-24 23:16:35 -0400322 let enum_item = {
David Tolnaye3d41b72017-12-31 15:24:00 -0500323 let variants = variants.1.iter().map(|v| {
Alex Crichtona74a1c82018-05-16 10:20:44 -0700324 let name = v.name.clone();
David Tolnayfcfb9002017-12-28 22:04:29 -0500325 match v.member {
326 Some(ref member) => quote!(#name(#member)),
327 None => quote!(#name),
328 }
Nika Layzell27726662017-10-24 23:16:35 -0400329 });
David Tolnayb7ccc4f2018-08-02 00:41:32 -0700330 parse_quote! {
331 pub enum #id {
332 #(#variants),*
333 }
334 }
Nika Layzell27726662017-10-24 23:16:35 -0400335 };
336 let mut items = vec![AstItem {
David Tolnay3d772182017-12-28 17:18:53 -0500337 ast: enum_item,
Nika Layzell27726662017-10-24 23:16:35 -0400338 features: quote!(),
339 eos_full: false,
340 }];
David Tolnaye3d41b72017-12-31 15:24:00 -0500341 items.extend(variants.1.into_iter().filter_map(|v| v.inner));
Nika Layzell27726662017-10-24 23:16:35 -0400342 AstEnumOfStructs(items)
343 })
David Tolnayab919512017-12-30 23:31:51 -0500344 ));
Nika Layzell27726662017-10-24 23:16:35 -0400345 }
346}
347
348mod codegen {
349 use super::{AstItem, Lookup};
David Tolnay6b46a702018-08-01 23:51:32 -0700350 use inflections::Inflect;
David Tolnay01ed0ce2018-05-20 20:18:14 -0700351 use proc_macro2::{Span, TokenStream};
David Tolnay6af48992018-08-01 11:16:28 -0700352 use quote::{ToTokens, TokenStreamExt};
David Tolnay01ed0ce2018-05-20 20:18:14 -0700353 use syn::punctuated::Punctuated;
David Tolnay7ac699c2018-08-24 14:00:58 -0400354 use syn::synom::ext::IdentExt;
355 use syn::synom::Parser;
David Tolnay01ed0ce2018-05-20 20:18:14 -0700356 use syn::*;
Nika Layzell27726662017-10-24 23:16:35 -0400357
358 #[derive(Default)]
359 pub struct State {
David Tolnay6af48992018-08-01 11:16:28 -0700360 pub visit_trait: TokenStream,
361 pub visit_impl: TokenStream,
362 pub visit_mut_trait: TokenStream,
363 pub visit_mut_impl: TokenStream,
364 pub fold_trait: TokenStream,
365 pub fold_impl: TokenStream,
Nika Layzell27726662017-10-24 23:16:35 -0400366 }
367
David Tolnay4a918742017-12-28 16:54:41 -0500368 fn under_name(name: Ident) -> Ident {
Alex Crichtona74a1c82018-05-16 10:20:44 -0700369 Ident::new(&name.to_string().to_snake_case(), Span::call_site())
Nika Layzell27726662017-10-24 23:16:35 -0400370 }
371
David Tolnay3d772182017-12-28 17:18:53 -0500372 enum RelevantType<'a> {
373 Box(&'a Type),
374 Vec(&'a Type),
David Tolnayf2cfd722017-12-31 18:02:51 -0500375 Punctuated(&'a Type),
David Tolnay3d772182017-12-28 17:18:53 -0500376 Option(&'a Type),
David Tolnayf2cfd722017-12-31 18:02:51 -0500377 Tuple(&'a Punctuated<Type, Token![,]>),
David Tolnay3d772182017-12-28 17:18:53 -0500378 Simple(&'a AstItem),
David Tolnay7ac699c2018-08-24 14:00:58 -0400379 TokenPunct(TokenStream),
380 TokenKeyword(TokenStream),
381 TokenGroup(Ident),
David Tolnay3d772182017-12-28 17:18:53 -0500382 Pass,
383 }
Nika Layzell27726662017-10-24 23:16:35 -0400384
David Tolnay3d772182017-12-28 17:18:53 -0500385 fn classify<'a>(ty: &'a Type, lookup: &'a Lookup) -> RelevantType<'a> {
386 match *ty {
David Tolnay01ed0ce2018-05-20 20:18:14 -0700387 Type::Path(TypePath {
388 qself: None,
389 ref path,
390 }) => {
David Tolnay56080682018-01-06 14:01:52 -0800391 let last = path.segments.last().unwrap().into_value();
Alex Crichtona74a1c82018-05-16 10:20:44 -0700392 match &last.ident.to_string()[..] {
David Tolnay3d772182017-12-28 17:18:53 -0500393 "Box" => RelevantType::Box(first_arg(&last.arguments)),
394 "Vec" => RelevantType::Vec(first_arg(&last.arguments)),
David Tolnayf2cfd722017-12-31 18:02:51 -0500395 "Punctuated" => RelevantType::Punctuated(first_arg(&last.arguments)),
David Tolnay3d772182017-12-28 17:18:53 -0500396 "Option" => RelevantType::Option(first_arg(&last.arguments)),
David Tolnay1e01f9c2017-12-28 20:16:19 -0500397 "Brace" | "Bracket" | "Paren" | "Group" => {
David Tolnay7ac699c2018-08-24 14:00:58 -0400398 RelevantType::TokenGroup(last.ident.clone())
David Tolnay1e01f9c2017-12-28 20:16:19 -0500399 }
David Tolnay3d772182017-12-28 17:18:53 -0500400 _ => {
401 if let Some(item) = lookup.get(&last.ident) {
402 RelevantType::Simple(item)
403 } else {
404 RelevantType::Pass
405 }
406 }
407 }
Nika Layzell27726662017-10-24 23:16:35 -0400408 }
David Tolnay01ed0ce2018-05-20 20:18:14 -0700409 Type::Tuple(TypeTuple { ref elems, .. }) => RelevantType::Tuple(elems),
410 Type::Macro(TypeMacro { ref mac })
411 if mac.path.segments.last().unwrap().into_value().ident == "Token" =>
412 {
David Tolnay7ac699c2018-08-24 14:00:58 -0400413 let is_ident = Ident::parse_any.parse2(mac.tts.clone()).is_ok() ;
414 let is_underscore = parse2::<Token![_]>(mac.tts.clone()).is_ok();
415 if is_ident && !is_underscore {
416 RelevantType::TokenKeyword(mac.into_token_stream())
417 } else {
418 RelevantType::TokenPunct(mac.into_token_stream())
419 }
David Tolnaycc0f0372017-12-28 19:11:04 -0500420 }
David Tolnay3d772182017-12-28 17:18:53 -0500421 _ => RelevantType::Pass,
Nika Layzell27726662017-10-24 23:16:35 -0400422 }
423 }
424
425 #[derive(Debug, Eq, PartialEq, Copy, Clone)]
426 enum Kind {
427 Visit,
428 VisitMut,
429 Fold,
430 }
431
David Tolnayf0d63bf2017-12-26 12:29:47 -0500432 enum Operand {
Alex Crichton715862b2018-05-17 12:31:49 -0700433 Borrowed(TokenStream),
434 Owned(TokenStream),
David Tolnayf0d63bf2017-12-26 12:29:47 -0500435 }
436
David Tolnay83db9272017-12-28 17:02:31 -0500437 use self::Kind::*;
David Tolnay01ed0ce2018-05-20 20:18:14 -0700438 use self::Operand::*;
David Tolnay83db9272017-12-28 17:02:31 -0500439
David Tolnayf0d63bf2017-12-26 12:29:47 -0500440 impl Operand {
Alex Crichton715862b2018-05-17 12:31:49 -0700441 fn tokens(&self) -> &TokenStream {
David Tolnayf0d63bf2017-12-26 12:29:47 -0500442 match *self {
David Tolnay83db9272017-12-28 17:02:31 -0500443 Borrowed(ref n) | Owned(ref n) => n,
David Tolnayf0d63bf2017-12-26 12:29:47 -0500444 }
445 }
446
Alex Crichton715862b2018-05-17 12:31:49 -0700447 fn ref_tokens(&self) -> TokenStream {
David Tolnayf0d63bf2017-12-26 12:29:47 -0500448 match *self {
David Tolnay83db9272017-12-28 17:02:31 -0500449 Borrowed(ref n) => n.clone(),
450 Owned(ref n) => quote!(&#n),
David Tolnayf0d63bf2017-12-26 12:29:47 -0500451 }
452 }
453
Alex Crichton715862b2018-05-17 12:31:49 -0700454 fn ref_mut_tokens(&self) -> TokenStream {
David Tolnayf0d63bf2017-12-26 12:29:47 -0500455 match *self {
David Tolnay83db9272017-12-28 17:02:31 -0500456 Borrowed(ref n) => n.clone(),
457 Owned(ref n) => quote!(&mut #n),
David Tolnayf0d63bf2017-12-26 12:29:47 -0500458 }
459 }
460
Alex Crichton715862b2018-05-17 12:31:49 -0700461 fn owned_tokens(&self) -> TokenStream {
David Tolnayf0d63bf2017-12-26 12:29:47 -0500462 match *self {
David Tolnay83db9272017-12-28 17:02:31 -0500463 Borrowed(ref n) => quote!(*#n),
464 Owned(ref n) => n.clone(),
David Tolnayf0d63bf2017-12-26 12:29:47 -0500465 }
466 }
467 }
468
Nika Layzellc08227a2017-12-04 16:30:17 -0500469 fn first_arg(params: &PathArguments) -> &Type {
Nika Layzell27726662017-10-24 23:16:35 -0400470 let data = match *params {
Nika Layzellc08227a2017-12-04 16:30:17 -0500471 PathArguments::AngleBracketed(ref data) => data,
472 _ => panic!("Expected at least 1 type argument here"),
Nika Layzell27726662017-10-24 23:16:35 -0400473 };
474
David Tolnay01ed0ce2018-05-20 20:18:14 -0700475 match **data
476 .args
David Tolnayd67fb752017-12-27 13:50:29 -0500477 .first()
478 .expect("Expected at least 1 type argument here")
David Tolnay56080682018-01-06 14:01:52 -0800479 .value()
David Tolnayd67fb752017-12-27 13:50:29 -0500480 {
David Tolnayea9ae892017-12-26 01:44:32 -0500481 GenericArgument::Type(ref ty) => ty,
Nika Layzellc08227a2017-12-04 16:30:17 -0500482 _ => panic!("Expected at least 1 type argument here"),
Nika Layzell357885a2017-12-04 15:47:07 -0500483 }
Nika Layzell27726662017-10-24 23:16:35 -0400484 }
485
David Tolnay6af48992018-08-01 11:16:28 -0700486 fn simple_visit(item: &AstItem, kind: Kind, name: &Operand) -> TokenStream {
487 let ident = under_name(item.ast.ident.clone());
488
David Tolnay4a918742017-12-28 16:54:41 -0500489 match kind {
David Tolnay6af48992018-08-01 11:16:28 -0700490 Visit => {
491 let method = Ident::new(&format!("visit_{}", ident), Span::call_site());
492 let name = name.ref_tokens();
493 quote! {
494 _visitor.#method(#name)
495 }
496 }
497 VisitMut => {
498 let method = Ident::new(&format!("visit_{}_mut", ident), Span::call_site());
499 let name = name.ref_mut_tokens();
500 quote! {
501 _visitor.#method(#name)
502 }
503 }
504 Fold => {
505 let method = Ident::new(&format!("fold_{}", ident), Span::call_site());
506 let name = name.owned_tokens();
507 quote! {
508 _visitor.#method(#name)
509 }
510 }
Nika Layzell27726662017-10-24 23:16:35 -0400511 }
512 }
513
David Tolnay6af48992018-08-01 11:16:28 -0700514 fn box_visit(elem: &Type, lookup: &Lookup, kind: Kind, name: &Operand) -> Option<TokenStream> {
David Tolnay4a918742017-12-28 16:54:41 -0500515 let name = name.owned_tokens();
David Tolnay39d0a202017-12-28 18:19:00 -0500516 let res = visit(elem, lookup, kind, &Owned(quote!(*#name)))?;
David Tolnay4a918742017-12-28 16:54:41 -0500517 Some(match kind {
David Tolnay6af48992018-08-01 11:16:28 -0700518 Fold => quote! {
519 Box::new(#res)
520 },
David Tolnay83db9272017-12-28 17:02:31 -0500521 Visit | VisitMut => res,
David Tolnay4a918742017-12-28 16:54:41 -0500522 })
Nika Layzell27726662017-10-24 23:16:35 -0400523 }
524
David Tolnay6af48992018-08-01 11:16:28 -0700525 fn vec_visit(elem: &Type, lookup: &Lookup, kind: Kind, name: &Operand) -> Option<TokenStream> {
David Tolnay4a918742017-12-28 16:54:41 -0500526 let operand = match kind {
David Tolnay83db9272017-12-28 17:02:31 -0500527 Visit | VisitMut => Borrowed(quote!(it)),
528 Fold => Owned(quote!(it)),
David Tolnay4a918742017-12-28 16:54:41 -0500529 };
David Tolnay39d0a202017-12-28 18:19:00 -0500530 let val = visit(elem, lookup, kind, &operand)?;
David Tolnay4a918742017-12-28 16:54:41 -0500531 Some(match kind {
David Tolnay6af48992018-08-01 11:16:28 -0700532 Visit => {
533 let name = name.ref_tokens();
534 quote! {
535 for it in #name {
536 #val
537 }
538 }
539 }
540 VisitMut => {
541 let name = name.ref_mut_tokens();
542 quote! {
543 for it in #name {
544 #val
545 }
546 }
547 }
548 Fold => {
549 let name = name.owned_tokens();
550 quote! {
551 FoldHelper::lift(#name, |it| { #val })
552 }
553 }
David Tolnay3d772182017-12-28 17:18:53 -0500554 })
555 }
556
David Tolnay6eff4da2018-01-01 20:27:45 -0800557 fn punctuated_visit(
David Tolnay3d772182017-12-28 17:18:53 -0500558 elem: &Type,
559 lookup: &Lookup,
560 kind: Kind,
561 name: &Operand,
David Tolnay6af48992018-08-01 11:16:28 -0700562 ) -> Option<TokenStream> {
David Tolnay3d772182017-12-28 17:18:53 -0500563 let operand = match kind {
564 Visit | VisitMut => Borrowed(quote!(it)),
565 Fold => Owned(quote!(it)),
566 };
David Tolnay39d0a202017-12-28 18:19:00 -0500567 let val = visit(elem, lookup, kind, &operand)?;
David Tolnay3d772182017-12-28 17:18:53 -0500568 Some(match kind {
David Tolnay6af48992018-08-01 11:16:28 -0700569 Visit => {
570 let name = name.ref_tokens();
571 quote! {
572 for el in Punctuated::pairs(#name) {
573 let it = el.value();
574 #val
575 }
576 }
577 }
578 VisitMut => {
579 let name = name.ref_mut_tokens();
580 quote! {
581 for mut el in Punctuated::pairs_mut(#name) {
582 let it = el.value_mut();
583 #val
584 }
585 }
586 }
587 Fold => {
588 let name = name.owned_tokens();
589 quote! {
590 FoldHelper::lift(#name, |it| { #val })
591 }
592 }
David Tolnay4a918742017-12-28 16:54:41 -0500593 })
Nika Layzell27726662017-10-24 23:16:35 -0400594 }
595
David Tolnay6af48992018-08-01 11:16:28 -0700596 fn option_visit(
597 elem: &Type,
598 lookup: &Lookup,
599 kind: Kind,
600 name: &Operand,
601 ) -> Option<TokenStream> {
David Tolnay4a918742017-12-28 16:54:41 -0500602 let it = match kind {
David Tolnay83db9272017-12-28 17:02:31 -0500603 Visit | VisitMut => Borrowed(quote!(it)),
604 Fold => Owned(quote!(it)),
David Tolnay4a918742017-12-28 16:54:41 -0500605 };
David Tolnay39d0a202017-12-28 18:19:00 -0500606 let val = visit(elem, lookup, kind, &it)?;
David Tolnay6af48992018-08-01 11:16:28 -0700607 let name = name.owned_tokens();
David Tolnay4a918742017-12-28 16:54:41 -0500608 Some(match kind {
David Tolnay6af48992018-08-01 11:16:28 -0700609 Visit => quote! {
610 if let Some(ref it) = #name {
611 #val
612 }
613 },
614 VisitMut => quote! {
615 if let Some(ref mut it) = #name {
616 #val
617 }
618 },
619 Fold => quote! {
620 (#name).map(|it| { #val })
621 },
David Tolnay4a918742017-12-28 16:54:41 -0500622 })
Nika Layzell4ab8d6e2017-10-26 09:45:49 -0400623 }
624
David Tolnay5c4c0b52017-12-28 17:58:54 -0500625 fn tuple_visit(
David Tolnayf2cfd722017-12-31 18:02:51 -0500626 elems: &Punctuated<Type, Token![,]>,
David Tolnay5c4c0b52017-12-28 17:58:54 -0500627 lookup: &Lookup,
628 kind: Kind,
629 name: &Operand,
David Tolnay6af48992018-08-01 11:16:28 -0700630 ) -> Option<TokenStream> {
631 if elems.is_empty() {
632 return None;
633 }
634
635 let mut code = TokenStream::new();
David Tolnayf047c7a2017-12-31 02:29:04 -0500636 for (i, elem) in elems.iter().enumerate() {
David Tolnay5c4c0b52017-12-28 17:58:54 -0500637 let name = name.tokens();
David Tolnay14982012017-12-29 00:49:51 -0500638 let i = Index::from(i);
David Tolnay5c4c0b52017-12-28 17:58:54 -0500639 let it = Owned(quote!((#name).#i));
David Tolnay01ed0ce2018-05-20 20:18:14 -0700640 let val = visit(elem, lookup, kind, &it).unwrap_or_else(|| noop_visit(kind, &it));
David Tolnay6af48992018-08-01 11:16:28 -0700641 code.append_all(val);
David Tolnay5c4c0b52017-12-28 17:58:54 -0500642 match kind {
David Tolnay6af48992018-08-01 11:16:28 -0700643 Fold => code.append_all(quote!(,)),
644 Visit | VisitMut => code.append_all(quote!(;)),
David Tolnay5c4c0b52017-12-28 17:58:54 -0500645 }
David Tolnay5c4c0b52017-12-28 17:58:54 -0500646 }
David Tolnay6af48992018-08-01 11:16:28 -0700647 Some(match kind {
648 Fold => quote! {
649 (#code)
650 },
651 Visit | VisitMut => code,
652 })
David Tolnay5c4c0b52017-12-28 17:58:54 -0500653 }
654
David Tolnay7ac699c2018-08-24 14:00:58 -0400655 fn token_punct_visit(ty: TokenStream, kind: Kind, name: &Operand) -> TokenStream {
David Tolnay6af48992018-08-01 11:16:28 -0700656 let name = name.tokens();
David Tolnaycc0f0372017-12-28 19:11:04 -0500657 match kind {
David Tolnay6af48992018-08-01 11:16:28 -0700658 Fold => quote! {
David Tolnay7ac699c2018-08-24 14:00:58 -0400659 #ty(tokens_helper(_visitor, &#name.spans))
David Tolnay6af48992018-08-01 11:16:28 -0700660 },
661 Visit => quote! {
David Tolnay7ac699c2018-08-24 14:00:58 -0400662 tokens_helper(_visitor, &#name.spans)
David Tolnay6af48992018-08-01 11:16:28 -0700663 },
664 VisitMut => quote! {
David Tolnay7ac699c2018-08-24 14:00:58 -0400665 tokens_helper(_visitor, &mut #name.spans)
666 },
667 }
668 }
669
670 fn token_keyword_visit(ty: TokenStream, kind: Kind, name: &Operand) -> TokenStream {
671 let name = name.tokens();
672 match kind {
673 Fold => quote! {
674 #ty(tokens_helper(_visitor, &#name.span))
675 },
676 Visit => quote! {
677 tokens_helper(_visitor, &#name.span)
678 },
679 VisitMut => quote! {
680 tokens_helper(_visitor, &mut #name.span)
681 },
682 }
683 }
684
685 fn token_group_visit(ty: Ident, kind: Kind, name: &Operand) -> TokenStream {
686 let name = name.tokens();
687 match kind {
688 Fold => quote! {
689 #ty(tokens_helper(_visitor, &#name.span))
690 },
691 Visit => quote! {
692 tokens_helper(_visitor, &#name.span)
693 },
694 VisitMut => quote! {
695 tokens_helper(_visitor, &mut #name.span)
David Tolnay6af48992018-08-01 11:16:28 -0700696 },
David Tolnaycc0f0372017-12-28 19:11:04 -0500697 }
698 }
699
David Tolnay6af48992018-08-01 11:16:28 -0700700 fn noop_visit(kind: Kind, name: &Operand) -> TokenStream {
David Tolnay4a918742017-12-28 16:54:41 -0500701 match kind {
David Tolnay6af48992018-08-01 11:16:28 -0700702 Fold => name.owned_tokens(),
703 Visit | VisitMut => {
704 let name = name.tokens();
705 quote! {
706 skip!(#name)
707 }
708 }
David Tolnay4a918742017-12-28 16:54:41 -0500709 }
710 }
711
David Tolnay6af48992018-08-01 11:16:28 -0700712 fn visit(ty: &Type, lookup: &Lookup, kind: Kind, name: &Operand) -> Option<TokenStream> {
David Tolnay3d772182017-12-28 17:18:53 -0500713 match classify(ty, lookup) {
David Tolnay01ed0ce2018-05-20 20:18:14 -0700714 RelevantType::Box(elem) => box_visit(elem, lookup, kind, name),
715 RelevantType::Vec(elem) => vec_visit(elem, lookup, kind, name),
716 RelevantType::Punctuated(elem) => punctuated_visit(elem, lookup, kind, name),
717 RelevantType::Option(elem) => option_visit(elem, lookup, kind, name),
718 RelevantType::Tuple(elems) => tuple_visit(elems, lookup, kind, name),
David Tolnay3d772182017-12-28 17:18:53 -0500719 RelevantType::Simple(item) => {
720 let mut res = simple_visit(item, kind, name);
721 Some(if item.eos_full {
David Tolnay6af48992018-08-01 11:16:28 -0700722 quote! {
723 full!(#res)
724 }
David Tolnay3d772182017-12-28 17:18:53 -0500725 } else {
726 res
727 })
728 }
David Tolnay7ac699c2018-08-24 14:00:58 -0400729 RelevantType::TokenPunct(ty) => Some(token_punct_visit(ty, kind, name)),
730 RelevantType::TokenKeyword(ty) => Some(token_keyword_visit(ty, kind, name)),
731 RelevantType::TokenGroup(ty) => Some(token_group_visit(ty, kind, name)),
David Tolnay01ed0ce2018-05-20 20:18:14 -0700732 RelevantType::Pass => None,
Nika Layzell27726662017-10-24 23:16:35 -0400733 }
Nika Layzell27726662017-10-24 23:16:35 -0400734 }
735
736 pub fn generate(state: &mut State, lookup: &Lookup, s: &AstItem) {
David Tolnay6af48992018-08-01 11:16:28 -0700737 let features = &s.features;
Alex Crichtona74a1c82018-05-16 10:20:44 -0700738 let under_name = under_name(s.ast.ident.clone());
David Tolnay6af48992018-08-01 11:16:28 -0700739 let ty = &s.ast.ident;
740 let visit_fn = Ident::new(&format!("visit_{}", under_name), Span::call_site());
741 let visit_mut_fn = Ident::new(&format!("visit_{}_mut", under_name), Span::call_site());
742 let fold_fn = Ident::new(&format!("fold_{}", under_name), Span::call_site());
Nika Layzell27726662017-10-24 23:16:35 -0400743
David Tolnay6af48992018-08-01 11:16:28 -0700744 let mut visit_impl = TokenStream::new();
745 let mut visit_mut_impl = TokenStream::new();
746 let mut fold_impl = TokenStream::new();
Nika Layzell27726662017-10-24 23:16:35 -0400747
David Tolnaye3d41b72017-12-31 15:24:00 -0500748 match s.ast.data {
749 Data::Enum(ref e) => {
David Tolnay6af48992018-08-01 11:16:28 -0700750 let mut visit_variants = TokenStream::new();
751 let mut visit_mut_variants = TokenStream::new();
752 let mut fold_variants = TokenStream::new();
753
Nika Layzell27726662017-10-24 23:16:35 -0400754 for variant in &e.variants {
David Tolnay6af48992018-08-01 11:16:28 -0700755 let variant_ident = &variant.ident;
756
757 match variant.fields {
David Tolnaye3d41b72017-12-31 15:24:00 -0500758 Fields::Named(..) => panic!("Doesn't support enum struct variants"),
759 Fields::Unnamed(ref fields) => {
David Tolnay6af48992018-08-01 11:16:28 -0700760 let mut bind_visit_fields = TokenStream::new();
761 let mut bind_visit_mut_fields = TokenStream::new();
762 let mut bind_fold_fields = TokenStream::new();
Nika Layzell27726662017-10-24 23:16:35 -0400763
David Tolnay6af48992018-08-01 11:16:28 -0700764 let mut visit_fields = TokenStream::new();
765 let mut visit_mut_fields = TokenStream::new();
766 let mut fold_fields = TokenStream::new();
Nika Layzell27726662017-10-24 23:16:35 -0400767
David Tolnay6af48992018-08-01 11:16:28 -0700768 for (idx, field) in fields.unnamed.iter().enumerate() {
769 let name = format!("_binding_{}", idx);
770 let binding = Ident::new(&name, Span::call_site());
Nika Layzell27726662017-10-24 23:16:35 -0400771
David Tolnay6af48992018-08-01 11:16:28 -0700772 bind_visit_fields.append_all(quote! {
773 ref #binding,
774 });
775 bind_visit_mut_fields.append_all(quote! {
776 ref mut #binding,
777 });
778 bind_fold_fields.append_all(quote! {
779 #binding,
780 });
Nika Layzell27726662017-10-24 23:16:35 -0400781
David Tolnay6af48992018-08-01 11:16:28 -0700782 let borrowed_binding = Borrowed(quote!(#binding));
783 let owned_binding = Owned(quote!(#binding));
Nika Layzell27726662017-10-24 23:16:35 -0400784
David Tolnay6af48992018-08-01 11:16:28 -0700785 visit_fields.append_all(
786 visit(&field.ty, lookup, Visit, &borrowed_binding)
787 .unwrap_or_else(|| noop_visit(Visit, &borrowed_binding)),
788 );
789 visit_mut_fields.append_all(
790 visit(&field.ty, lookup, VisitMut, &borrowed_binding)
791 .unwrap_or_else(|| noop_visit(VisitMut, &borrowed_binding)),
792 );
793 fold_fields.append_all(
794 visit(&field.ty, lookup, Fold, &owned_binding)
795 .unwrap_or_else(|| noop_visit(Fold, &owned_binding)),
796 );
Nika Layzell27726662017-10-24 23:16:35 -0400797
David Tolnay6af48992018-08-01 11:16:28 -0700798 visit_fields.append_all(quote!(;));
799 visit_mut_fields.append_all(quote!(;));
800 fold_fields.append_all(quote!(,));
801 }
Nika Layzell27726662017-10-24 23:16:35 -0400802
David Tolnay6af48992018-08-01 11:16:28 -0700803 visit_variants.append_all(quote! {
804 #ty::#variant_ident(#bind_visit_fields) => {
805 #visit_fields
806 }
807 });
808
809 visit_mut_variants.append_all(quote! {
810 #ty::#variant_ident(#bind_visit_mut_fields) => {
811 #visit_mut_fields
812 }
813 });
814
815 fold_variants.append_all(quote! {
816 #ty::#variant_ident(#bind_fold_fields) => {
817 #ty::#variant_ident(
818 #fold_fields
819 )
820 }
821 });
Nika Layzell27726662017-10-24 23:16:35 -0400822 }
David Tolnaye3d41b72017-12-31 15:24:00 -0500823 Fields::Unit => {
David Tolnay6af48992018-08-01 11:16:28 -0700824 visit_variants.append_all(quote! {
825 #ty::#variant_ident => {}
826 });
827 visit_mut_variants.append_all(quote! {
828 #ty::#variant_ident => {}
829 });
830 fold_variants.append_all(quote! {
831 #ty::#variant_ident => {
832 #ty::#variant_ident
833 }
834 });
Nika Layzell27726662017-10-24 23:16:35 -0400835 }
Nika Layzell27726662017-10-24 23:16:35 -0400836 }
Nika Layzell27726662017-10-24 23:16:35 -0400837 }
David Tolnay6af48992018-08-01 11:16:28 -0700838
839 visit_impl.append_all(quote! {
840 match *_i {
841 #visit_variants
842 }
843 });
844
845 visit_mut_impl.append_all(quote! {
846 match *_i {
847 #visit_mut_variants
848 }
849 });
850
851 fold_impl.append_all(quote! {
852 match _i {
853 #fold_variants
854 }
855 });
Nika Layzell27726662017-10-24 23:16:35 -0400856 }
David Tolnaye3d41b72017-12-31 15:24:00 -0500857 Data::Struct(ref v) => {
David Tolnay6af48992018-08-01 11:16:28 -0700858 let mut fold_fields = TokenStream::new();
859
David Tolnaybc5b2c02018-08-02 00:18:23 -0700860 for (idx, field) in v.fields.iter().enumerate() {
861 let id = match field.ident {
862 Some(ref ident) => Member::Named(ident.clone()),
863 None => Member::Unnamed(Index::from(idx)),
864 };
865 let ref_toks = Owned(quote!(_i.#id));
David Tolnay6af48992018-08-01 11:16:28 -0700866 let visit_field = visit(&field.ty, lookup, Visit, &ref_toks)
867 .unwrap_or_else(|| noop_visit(Visit, &ref_toks));
868 visit_impl.append_all(quote! {
869 #visit_field;
870 });
871 let visit_mut_field = visit(&field.ty, lookup, VisitMut, &ref_toks)
872 .unwrap_or_else(|| noop_visit(VisitMut, &ref_toks));
873 visit_mut_impl.append_all(quote! {
874 #visit_mut_field;
875 });
David Tolnay83db9272017-12-28 17:02:31 -0500876 let fold = visit(&field.ty, lookup, Fold, &ref_toks)
David Tolnay01ed0ce2018-05-20 20:18:14 -0700877 .unwrap_or_else(|| noop_visit(Fold, &ref_toks));
Nika Layzell27726662017-10-24 23:16:35 -0400878 if let Some(ref name) = field.ident {
David Tolnay6af48992018-08-01 11:16:28 -0700879 fold_fields.append_all(quote! {
880 #name: #fold,
881 });
Nika Layzell27726662017-10-24 23:16:35 -0400882 } else {
David Tolnay6af48992018-08-01 11:16:28 -0700883 fold_fields.append_all(quote! {
884 #fold,
885 });
Nika Layzell27726662017-10-24 23:16:35 -0400886 }
887 }
888
David Tolnaye3d41b72017-12-31 15:24:00 -0500889 match v.fields {
David Tolnay6af48992018-08-01 11:16:28 -0700890 Fields::Named(..) | Fields::Unnamed(..) => fold_impl.append_all(quote! {
891 #ty {
892 #fold_fields
893 }
894 }),
895 Fields::Unit => {
896 if ty == "Ident" {
897 fold_impl.append_all(quote! {
898 let mut _i = _i;
899 let span = _visitor.fold_span(_i.span());
900 _i.set_span(span);
901 });
902 }
903 fold_impl.append_all(quote! {
904 _i
905 });
906 }
Nika Layzell27726662017-10-24 23:16:35 -0400907 };
908 }
David Tolnaye3d41b72017-12-31 15:24:00 -0500909 Data::Union(..) => panic!("Union not supported"),
Nika Layzell27726662017-10-24 23:16:35 -0400910 }
911
David Tolnay6af48992018-08-01 11:16:28 -0700912 let mut include_fold_impl = true;
David Tolnay360efd22018-01-04 23:35:26 -0800913 if let Data::Struct(ref data) = s.ast.data {
914 if let Fields::Named(ref fields) = data.fields {
David Tolnay01ed0ce2018-05-20 20:18:14 -0700915 if fields
916 .named
917 .iter()
918 .any(|field| field.vis == Visibility::Inherited)
919 {
David Tolnay360efd22018-01-04 23:35:26 -0800920 // Discard the generated impl if there are private fields.
921 // These have to be handwritten.
David Tolnay6af48992018-08-01 11:16:28 -0700922 include_fold_impl = false;
David Tolnay360efd22018-01-04 23:35:26 -0800923 }
924 }
David Tolnayd0adf522017-12-29 01:30:07 -0500925 }
David Tolnay6af48992018-08-01 11:16:28 -0700926
927 state.visit_trait.append_all(quote! {
928 #features
929 fn #visit_fn(&mut self, i: &'ast #ty) {
930 #visit_fn(self, i)
931 }
932 });
933
934 state.visit_impl.append_all(quote! {
935 #features
936 pub fn #visit_fn<'ast, V: Visit<'ast> + ?Sized>(
937 _visitor: &mut V, _i: &'ast #ty
938 ) {
939 #visit_impl
940 }
941 });
942
943 state.visit_mut_trait.append_all(quote! {
944 #features
945 fn #visit_mut_fn(&mut self, i: &mut #ty) {
946 #visit_mut_fn(self, i)
947 }
948 });
949
950 state.visit_mut_impl.append_all(quote! {
951 #features
952 pub fn #visit_mut_fn<V: VisitMut + ?Sized>(
953 _visitor: &mut V, _i: &mut #ty
954 ) {
955 #visit_mut_impl
956 }
957 });
958
959 state.fold_trait.append_all(quote! {
960 #features
961 fn #fold_fn(&mut self, i: #ty) -> #ty {
962 #fold_fn(self, i)
963 }
964 });
965
966 if include_fold_impl {
967 state.fold_impl.append_all(quote! {
968 #features
969 pub fn #fold_fn<V: Fold + ?Sized>(
970 _visitor: &mut V, _i: #ty
971 ) -> #ty {
972 #fold_impl
973 }
974 });
975 }
Nika Layzell27726662017-10-24 23:16:35 -0400976 }
977}
978
David Tolnay6af48992018-08-01 11:16:28 -0700979fn write_file(path: &str, content: TokenStream) {
David Tolnay8c81f622018-07-31 23:34:35 -0700980 let mut file = File::create(path).unwrap();
David Tolnay6af48992018-08-01 11:16:28 -0700981 write!(
982 file,
983 "// THIS FILE IS AUTOMATICALLY GENERATED; DO NOT EDIT\n\n"
984 ).unwrap();
David Tolnay8c81f622018-07-31 23:34:35 -0700985 let mut config = rustfmt::Config::default();
986 config.set().emit_mode(rustfmt::EmitMode::Stdout);
987 config.set().verbose(rustfmt::Verbosity::Quiet);
David Tolnay280202f2018-08-02 00:29:54 -0700988 config.set().format_macro_matchers(true);
David Tolnay8c81f622018-07-31 23:34:35 -0700989 let mut session = rustfmt::Session::new(config, Some(&mut file));
David Tolnay6af48992018-08-01 11:16:28 -0700990 session
991 .format(rustfmt::Input::Text(content.to_string()))
992 .unwrap();
David Tolnay8c81f622018-07-31 23:34:35 -0700993}
994
Nika Layzell27726662017-10-24 23:16:35 -0400995fn main() {
996 let mut lookup = BTreeMap::new();
David Tolnayea9ae892017-12-26 01:44:32 -0500997 load_file(SYN_CRATE_ROOT, &quote!(), &mut lookup).unwrap();
Nika Layzell27726662017-10-24 23:16:35 -0400998
Nika Layzellefb83ba2017-12-19 18:23:55 -0500999 // Load in any terminal types
1000 for &tt in TERMINAL_TYPES {
1001 use syn::*;
David Tolnayd67fb752017-12-27 13:50:29 -05001002 lookup.insert(
Alex Crichtona74a1c82018-05-16 10:20:44 -07001003 Ident::new(&tt, Span::call_site()),
David Tolnayd67fb752017-12-27 13:50:29 -05001004 AstItem {
David Tolnay3d772182017-12-28 17:18:53 -05001005 ast: DeriveInput {
Alex Crichtona74a1c82018-05-16 10:20:44 -07001006 ident: Ident::new(tt, Span::call_site()),
David Tolnayd67fb752017-12-27 13:50:29 -05001007 vis: Visibility::Public(VisPublic {
David Tolnay6af48992018-08-01 11:16:28 -07001008 pub_token: <Token![pub]>::default(),
David Tolnayd67fb752017-12-27 13:50:29 -05001009 }),
1010 attrs: vec![],
David Tolnay6af48992018-08-01 11:16:28 -07001011 generics: Generics::default(),
David Tolnaye3d41b72017-12-31 15:24:00 -05001012 data: Data::Struct(DataStruct {
1013 fields: Fields::Unit,
David Tolnay6af48992018-08-01 11:16:28 -07001014 struct_token: <Token![struct]>::default(),
David Tolnayd67fb752017-12-27 13:50:29 -05001015 semi_token: None,
1016 }),
1017 },
hcplaa511792018-05-29 07:13:01 +03001018 features: TokenStream::new(),
David Tolnayd67fb752017-12-27 13:50:29 -05001019 eos_full: false,
Nika Layzellefb83ba2017-12-19 18:23:55 -05001020 },
David Tolnayd67fb752017-12-27 13:50:29 -05001021 );
Nika Layzellefb83ba2017-12-19 18:23:55 -05001022 }
1023
David Tolnay6af48992018-08-01 11:16:28 -07001024 let mut state = codegen::State::default();
Nika Layzell27726662017-10-24 23:16:35 -04001025 for s in lookup.values() {
1026 codegen::generate(&mut state, &lookup, s);
1027 }
1028
David Tolnay6af48992018-08-01 11:16:28 -07001029 let full_macro = quote! {
1030 #[cfg(feature = "full")]
1031 macro_rules! full {
1032 ($e:expr) => {
1033 $e
1034 };
1035 }
Nika Layzell4ab8d6e2017-10-26 09:45:49 -04001036
David Tolnay6af48992018-08-01 11:16:28 -07001037 #[cfg(all(feature = "derive", not(feature = "full")))]
1038 macro_rules! full {
1039 ($e:expr) => {
1040 unreachable!()
1041 };
1042 }
1043 };
Nika Layzell4ab8d6e2017-10-26 09:45:49 -04001044
David Tolnay6af48992018-08-01 11:16:28 -07001045 let skip_macro = quote! {
1046 #[cfg(any(feature = "full", feature = "derive"))]
1047 macro_rules! skip {
1048 ($($tt:tt)*) => {};
1049 }
1050 };
1051
1052 let fold_trait = state.fold_trait;
1053 let fold_impl = state.fold_impl;
David Tolnayae0009e2018-08-01 00:40:00 -07001054 write_file(
1055 FOLD_SRC,
David Tolnay6af48992018-08-01 11:16:28 -07001056 quote! {
1057 // Unreachable code is generated sometimes without the full feature.
1058 #![allow(unreachable_code)]
1059 #![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
Nika Layzell27726662017-10-24 23:16:35 -04001060
David Tolnay6af48992018-08-01 11:16:28 -07001061 use *;
1062 #[cfg(any(feature = "full", feature = "derive"))]
1063 use token::{Brace, Bracket, Paren, Group};
1064 use proc_macro2::Span;
1065 #[cfg(any(feature = "full", feature = "derive"))]
1066 use gen::helper::fold::*;
Nika Layzell27726662017-10-24 23:16:35 -04001067
David Tolnay6af48992018-08-01 11:16:28 -07001068 #full_macro
Nika Layzell27726662017-10-24 23:16:35 -04001069
David Tolnay6af48992018-08-01 11:16:28 -07001070 /// Syntax tree traversal to transform the nodes of an owned syntax tree.
1071 ///
1072 /// See the [module documentation] for details.
1073 ///
1074 /// [module documentation]: index.html
1075 ///
1076 /// *This trait is available if Syn is built with the `"fold"` feature.*
1077 pub trait Fold {
1078 #fold_trait
1079 }
Nika Layzell4ab8d6e2017-10-26 09:45:49 -04001080
David Tolnay6af48992018-08-01 11:16:28 -07001081 #[cfg(any(feature = "full", feature = "derive"))]
1082 macro_rules! fold_span_only {
1083 ($f:ident : $t:ident) => {
1084 pub fn $f<V: Fold + ?Sized>(_visitor: &mut V, mut _i: $t) -> $t {
1085 let span = _visitor.fold_span(_i.span());
1086 _i.set_span(span);
1087 _i
1088 }
1089 }
1090 }
Nika Layzell27726662017-10-24 23:16:35 -04001091
David Tolnay6af48992018-08-01 11:16:28 -07001092 #[cfg(any(feature = "full", feature = "derive"))]
1093 fold_span_only!(fold_lit_byte: LitByte);
1094 #[cfg(any(feature = "full", feature = "derive"))]
1095 fold_span_only!(fold_lit_byte_str: LitByteStr);
1096 #[cfg(any(feature = "full", feature = "derive"))]
1097 fold_span_only!(fold_lit_char: LitChar);
1098 #[cfg(any(feature = "full", feature = "derive"))]
1099 fold_span_only!(fold_lit_float: LitFloat);
1100 #[cfg(any(feature = "full", feature = "derive"))]
1101 fold_span_only!(fold_lit_int: LitInt);
1102 #[cfg(any(feature = "full", feature = "derive"))]
1103 fold_span_only!(fold_lit_str: LitStr);
David Tolnayd0adf522017-12-29 01:30:07 -05001104
David Tolnay6af48992018-08-01 11:16:28 -07001105 #fold_impl
1106 },
David Tolnayae0009e2018-08-01 00:40:00 -07001107 );
Nika Layzell27726662017-10-24 23:16:35 -04001108
David Tolnay6af48992018-08-01 11:16:28 -07001109 let visit_trait = state.visit_trait;
1110 let visit_impl = state.visit_impl;
David Tolnayae0009e2018-08-01 00:40:00 -07001111 write_file(
1112 VISIT_SRC,
David Tolnay6af48992018-08-01 11:16:28 -07001113 quote! {
1114 #![cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
Nika Layzell27726662017-10-24 23:16:35 -04001115
David Tolnay6af48992018-08-01 11:16:28 -07001116 use *;
1117 #[cfg(any(feature = "full", feature = "derive"))]
1118 use punctuated::Punctuated;
1119 use proc_macro2::Span;
1120 #[cfg(any(feature = "full", feature = "derive"))]
1121 use gen::helper::visit::*;
David Tolnayf0d63bf2017-12-26 12:29:47 -05001122
David Tolnay6af48992018-08-01 11:16:28 -07001123 #full_macro
1124 #skip_macro
Nika Layzell27726662017-10-24 23:16:35 -04001125
David Tolnay6af48992018-08-01 11:16:28 -07001126 /// Syntax tree traversal to walk a shared borrow of a syntax tree.
1127 ///
1128 /// See the [module documentation] for details.
1129 ///
1130 /// [module documentation]: index.html
1131 ///
1132 /// *This trait is available if Syn is built with the `"visit"` feature.*
1133 pub trait Visit<'ast> {
1134 #visit_trait
1135 }
Nika Layzell4ab8d6e2017-10-26 09:45:49 -04001136
David Tolnay6af48992018-08-01 11:16:28 -07001137 #visit_impl
1138 },
David Tolnayae0009e2018-08-01 00:40:00 -07001139 );
Nika Layzell27726662017-10-24 23:16:35 -04001140
David Tolnay6af48992018-08-01 11:16:28 -07001141 let visit_mut_trait = state.visit_mut_trait;
1142 let visit_mut_impl = state.visit_mut_impl;
David Tolnayae0009e2018-08-01 00:40:00 -07001143 write_file(
1144 VISIT_MUT_SRC,
David Tolnay6af48992018-08-01 11:16:28 -07001145 quote! {
1146 #![cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
Nika Layzell27726662017-10-24 23:16:35 -04001147
David Tolnay6af48992018-08-01 11:16:28 -07001148 use *;
1149 #[cfg(any(feature = "full", feature = "derive"))]
1150 use punctuated::Punctuated;
1151 use proc_macro2::Span;
1152 #[cfg(any(feature = "full", feature = "derive"))]
1153 use gen::helper::visit_mut::*;
David Tolnayf0d63bf2017-12-26 12:29:47 -05001154
David Tolnay6af48992018-08-01 11:16:28 -07001155 #full_macro
1156 #skip_macro
Nika Layzell27726662017-10-24 23:16:35 -04001157
David Tolnay6af48992018-08-01 11:16:28 -07001158 /// Syntax tree traversal to mutate an exclusive borrow of a syntax tree in
1159 /// place.
1160 ///
1161 /// See the [module documentation] for details.
1162 ///
1163 /// [module documentation]: index.html
1164 ///
1165 /// *This trait is available if Syn is built with the `"visit-mut"` feature.*
1166 pub trait VisitMut {
1167 #visit_mut_trait
1168 }
Nika Layzell4ab8d6e2017-10-26 09:45:49 -04001169
David Tolnay6af48992018-08-01 11:16:28 -07001170 #visit_mut_impl
1171 },
David Tolnayae0009e2018-08-01 00:40:00 -07001172 );
Nika Layzell27726662017-10-24 23:16:35 -04001173}