blob: 08aefd148cea249bc226db5cfe0d0c8276c573eb [file] [log] [blame]
Carl Lerche058ff472019-02-13 16:23:52 -08001use crate::types;
2
David Tolnay14d463e2019-02-15 14:23:51 -08003use indexmap::IndexMap;
David Tolnay397bd0b2019-02-15 20:51:10 -08004use quote::quote;
5use syn::{parse_quote, Data, DataStruct, DeriveInput, Ident, Item};
Carl Lerche058ff472019-02-13 16:23:52 -08006
7use std::collections::BTreeMap;
8use std::fs::File;
9use std::io::Read;
10use std::path::Path;
11
12const SYN_CRATE_ROOT: &str = "../src/lib.rs";
13const TOKEN_SRC: &str = "../src/token.rs";
14const IGNORED_MODS: &[&str] = &["fold", "visit", "visit_mut"];
15const EXTRA_TYPES: &[&str] = &["Lifetime"];
Carl Lerche058ff472019-02-13 16:23:52 -080016
17// NOTE: BTreeMap is used here instead of HashMap to have deterministic output.
18type ItemLookup = BTreeMap<Ident, AstItem>;
19type TokenLookup = BTreeMap<String, String>;
20
21/// Parse the contents of `src` and return a list of AST types.
David Tolnayf9bb8ff2019-02-15 13:10:14 -080022pub fn parse() -> types::Definitions {
Carl Lerche058ff472019-02-13 16:23:52 -080023 let mut item_lookup = BTreeMap::new();
24 load_file(SYN_CRATE_ROOT, &[], &mut item_lookup).unwrap();
25
26 let token_lookup = load_token_file(TOKEN_SRC).unwrap();
27
David Tolnayf9bb8ff2019-02-15 13:10:14 -080028 let types = item_lookup
Carl Lerche058ff472019-02-13 16:23:52 -080029 .values()
30 .map(|item| introspect_item(item, &item_lookup, &token_lookup))
David Tolnayf9bb8ff2019-02-15 13:10:14 -080031 .collect();
32
David Tolnay47fe7402019-02-15 14:35:25 -080033 let tokens = token_lookup
34 .into_iter()
35 .map(|(name, ty)| (ty, name))
36 .collect();
David Tolnayf9bb8ff2019-02-15 13:10:14 -080037
38 types::Definitions { types, tokens }
Carl Lerche058ff472019-02-13 16:23:52 -080039}
40
41/// Data extracted from syn source
42#[derive(Clone)]
43pub struct AstItem {
44 ast: DeriveInput,
45 features: Vec<syn::Attribute>,
46}
47
David Tolnayf9bb8ff2019-02-15 13:10:14 -080048fn introspect_item(item: &AstItem, items: &ItemLookup, tokens: &TokenLookup) -> types::Node {
Carl Lerche058ff472019-02-13 16:23:52 -080049 let features = introspect_features(&item.features);
50
51 match &item.ast.data {
David Tolnayc2be7b22019-02-15 18:48:31 -080052 Data::Enum(ref data) => types::Node {
53 ident: item.ast.ident.to_string(),
Carl Lerche058ff472019-02-13 16:23:52 -080054 features,
David Tolnayc2be7b22019-02-15 18:48:31 -080055 data: types::Data::Enum(introspect_enum(data, items, tokens)),
56 },
57 Data::Struct(ref data) => types::Node {
58 ident: item.ast.ident.to_string(),
Carl Lerche058ff472019-02-13 16:23:52 -080059 features,
David Tolnayc2be7b22019-02-15 18:48:31 -080060 data: {
61 if data.fields.iter().all(|f| is_pub(&f.vis)) {
62 types::Data::Struct(introspect_struct(data, items, tokens))
63 } else {
64 types::Data::Private
65 }
66 },
67 },
Carl Lerche058ff472019-02-13 16:23:52 -080068 Data::Union(..) => panic!("Union not supported"),
69 }
70}
71
72fn introspect_enum(
Carl Lerche058ff472019-02-13 16:23:52 -080073 item: &syn::DataEnum,
74 items: &ItemLookup,
75 tokens: &TokenLookup,
David Tolnay75c5a172019-02-15 20:35:41 -080076) -> types::Variants {
David Tolnayc2be7b22019-02-15 18:48:31 -080077 item.variants
Carl Lerche058ff472019-02-13 16:23:52 -080078 .iter()
79 .map(|variant| {
80 let fields = match &variant.fields {
81 syn::Fields::Unnamed(fields) => fields
82 .unnamed
83 .iter()
84 .map(|field| introspect_type(&field.ty, items, tokens))
85 .collect(),
86 syn::Fields::Unit => vec![],
87 _ => panic!("Enum representation not supported"),
88 };
89
David Tolnay75c5a172019-02-15 20:35:41 -080090 (variant.ident.to_string(), fields)
Carl Lerche058ff472019-02-13 16:23:52 -080091 })
David Tolnayc2be7b22019-02-15 18:48:31 -080092 .collect()
Carl Lerche058ff472019-02-13 16:23:52 -080093}
94
95fn introspect_struct(
Carl Lerche058ff472019-02-13 16:23:52 -080096 item: &syn::DataStruct,
97 items: &ItemLookup,
98 tokens: &TokenLookup,
David Tolnay75c5a172019-02-15 20:35:41 -080099) -> types::Fields {
David Tolnayc2be7b22019-02-15 18:48:31 -0800100 match &item.fields {
Carl Lerche058ff472019-02-13 16:23:52 -0800101 syn::Fields::Named(fields) => fields
102 .named
103 .iter()
104 .map(|field| {
David Tolnay14d463e2019-02-15 14:23:51 -0800105 (
Carl Lerche058ff472019-02-13 16:23:52 -0800106 field.ident.as_ref().unwrap().to_string(),
107 introspect_type(&field.ty, items, tokens),
108 )
109 })
110 .collect(),
David Tolnay14d463e2019-02-15 14:23:51 -0800111 syn::Fields::Unit => IndexMap::new(),
Carl Lerche058ff472019-02-13 16:23:52 -0800112 _ => panic!("Struct representation not supported"),
David Tolnayc2be7b22019-02-15 18:48:31 -0800113 }
Carl Lerche058ff472019-02-13 16:23:52 -0800114}
115
116fn introspect_type(item: &syn::Type, items: &ItemLookup, tokens: &TokenLookup) -> types::Type {
117 match item {
118 syn::Type::Path(syn::TypePath {
119 qself: None,
120 ref path,
121 }) => {
122 let last = path.segments.last().unwrap().into_value();
David Tolnay15730f22019-02-15 20:46:56 -0800123 let string = last.ident.to_string();
Carl Lerche058ff472019-02-13 16:23:52 -0800124
David Tolnay15730f22019-02-15 20:46:56 -0800125 match string.as_str() {
Carl Lerche058ff472019-02-13 16:23:52 -0800126 "Option" => {
127 let nested = introspect_type(first_arg(&last.arguments), items, tokens);
128 types::Type::Option(Box::new(nested))
129 }
130 "Punctuated" => {
131 let nested = introspect_type(first_arg(&last.arguments), items, tokens);
132 let punct = match introspect_type(last_arg(&last.arguments), items, tokens) {
133 types::Type::Token(s) => s,
134 _ => panic!(),
135 };
136
David Tolnayfa67ab02019-02-15 20:17:30 -0800137 types::Type::Punctuated(types::Punctuated {
138 element: Box::new(nested),
139 punct,
140 })
Carl Lerche058ff472019-02-13 16:23:52 -0800141 }
142 "Vec" => {
143 let nested = introspect_type(first_arg(&last.arguments), items, tokens);
144 types::Type::Vec(Box::new(nested))
145 }
146 "Box" => {
147 let nested = introspect_type(first_arg(&last.arguments), items, tokens);
148 types::Type::Box(Box::new(nested))
149 }
David Tolnay15730f22019-02-15 20:46:56 -0800150 "Brace" | "Bracket" | "Paren" | "Group" => types::Type::Group(string),
151 "TokenStream" | "Literal" | "Ident" | "Span" => types::Type::Ext(string),
152 "String" | "u32" | "usize" | "bool" => types::Type::Std(string),
Carl Lerche058ff472019-02-13 16:23:52 -0800153 _ => {
154 if items.get(&last.ident).is_some() {
David Tolnay15730f22019-02-15 20:46:56 -0800155 types::Type::Syn(string)
Carl Lerche058ff472019-02-13 16:23:52 -0800156 } else {
David Tolnay15730f22019-02-15 20:46:56 -0800157 unimplemented!("{}", string);
Carl Lerche058ff472019-02-13 16:23:52 -0800158 }
159 }
160 }
161 }
162 syn::Type::Tuple(syn::TypeTuple { ref elems, .. }) => {
163 let tys = elems
164 .iter()
165 .map(|ty| introspect_type(&ty, items, tokens))
166 .collect();
167 types::Type::Tuple(tys)
168 }
169 syn::Type::Macro(syn::TypeMacro { ref mac })
170 if mac.path.segments.last().unwrap().into_value().ident == "Token" =>
171 {
172 let content = mac.tts.to_string();
173 let ty = tokens.get(&content).unwrap().to_string();
174
David Tolnay157c7eb2019-02-15 13:21:48 -0800175 types::Type::Token(ty)
Carl Lerche058ff472019-02-13 16:23:52 -0800176 }
177 _ => panic!("{}", quote!(#item).to_string()),
178 }
179}
180
181fn introspect_features(attrs: &[syn::Attribute]) -> types::Features {
182 let mut ret = types::Features::default();
183
184 for attr in attrs {
185 if !attr.path.is_ident("cfg") {
186 continue;
187 }
188
189 let features: types::Features = syn::parse2(attr.tts.clone()).unwrap();
David Tolnay440fe582019-02-15 20:23:14 -0800190
191 if ret.any.is_empty() {
192 ret = features;
193 } else if ret.any.len() < features.any.len() {
194 assert!(ret.any.iter().all(|f| features.any.contains(f)));
195 } else {
196 assert!(features.any.iter().all(|f| ret.any.contains(f)));
197 ret = features;
198 }
Carl Lerche058ff472019-02-13 16:23:52 -0800199 }
200
201 ret
202}
203
204fn is_pub(vis: &syn::Visibility) -> bool {
205 match vis {
206 syn::Visibility::Public(_) => true,
207 _ => false,
208 }
209}
210
211fn first_arg(params: &syn::PathArguments) -> &syn::Type {
212 let data = match *params {
213 syn::PathArguments::AngleBracketed(ref data) => data,
214 _ => panic!("Expected at least 1 type argument here"),
215 };
216
217 match **data
218 .args
219 .first()
220 .expect("Expected at least 1 type argument here")
221 .value()
222 {
223 syn::GenericArgument::Type(ref ty) => ty,
224 _ => panic!("Expected at least 1 type argument here"),
225 }
226}
227
228fn last_arg(params: &syn::PathArguments) -> &syn::Type {
229 let data = match *params {
230 syn::PathArguments::AngleBracketed(ref data) => data,
231 _ => panic!("Expected at least 1 type argument here"),
232 };
233
234 match **data
235 .args
236 .last()
237 .expect("Expected at least 1 type argument here")
238 .value()
239 {
240 syn::GenericArgument::Type(ref ty) => ty,
241 _ => panic!("Expected at least 1 type argument here"),
242 }
243}
244
245mod parsing {
246 use super::{AstItem, TokenLookup};
247 use crate::types;
248
249 use proc_macro2::TokenStream;
David Tolnay397bd0b2019-02-15 20:51:10 -0800250 use quote::quote;
Carl Lerche058ff472019-02-13 16:23:52 -0800251 use syn;
252 use syn::parse::{Parse, ParseStream, Result};
253 use syn::*;
254
David Tolnay440fe582019-02-15 20:23:14 -0800255 use std::collections::{BTreeMap, BTreeSet};
Carl Lerche058ff472019-02-13 16:23:52 -0800256
257 fn peek_tag(input: ParseStream, tag: &str) -> bool {
258 let ahead = input.fork();
259 ahead.parse::<Token![#]>().is_ok()
260 && ahead
261 .parse::<Ident>()
262 .map(|ident| ident == tag)
263 .unwrap_or(false)
264 }
265
266 // Parses #full - returns #[cfg(feature = "full")] if it is present, and
267 // nothing otherwise.
268 fn full(input: ParseStream) -> Vec<syn::Attribute> {
269 if peek_tag(input, "full") {
270 input.parse::<Token![#]>().unwrap();
271 input.parse::<Ident>().unwrap();
272 vec![parse_quote!(#[cfg(feature = "full")])]
273 } else {
274 vec![]
275 }
276 }
277
278 fn skip_manual_extra_traits(input: ParseStream) {
279 if peek_tag(input, "manual_extra_traits") {
280 input.parse::<Token![#]>().unwrap();
281 input.parse::<Ident>().unwrap();
282 }
283 }
284
285 // Parses a simple AstStruct without the `pub struct` prefix.
286 fn ast_struct_inner(input: ParseStream) -> Result<AstItem> {
287 let ident: Ident = input.parse()?;
288 let features = full(input);
289 skip_manual_extra_traits(input);
290 let rest: TokenStream = input.parse()?;
291 Ok(AstItem {
292 ast: syn::parse2(quote! {
293 pub struct #ident #rest
294 })?,
295 features,
296 })
297 }
298
299 // ast_struct! parsing
300 pub struct AstStruct(pub(super) Vec<AstItem>);
301 impl Parse for AstStruct {
302 fn parse(input: ParseStream) -> Result<Self> {
303 input.call(Attribute::parse_outer)?;
304 input.parse::<Token![pub]>()?;
305 input.parse::<Token![struct]>()?;
306 let res = input.call(ast_struct_inner)?;
307 Ok(AstStruct(vec![res]))
308 }
309 }
310
311 fn no_visit(input: ParseStream) -> bool {
312 if peek_tag(input, "no_visit") {
313 input.parse::<Token![#]>().unwrap();
314 input.parse::<Ident>().unwrap();
315 true
316 } else {
317 false
318 }
319 }
320
321 // ast_enum! parsing
322 pub struct AstEnum(pub Vec<AstItem>);
323 impl Parse for AstEnum {
324 fn parse(input: ParseStream) -> Result<Self> {
325 input.call(Attribute::parse_outer)?;
326 input.parse::<Token![pub]>()?;
327 input.parse::<Token![enum]>()?;
328 let ident: Ident = input.parse()?;
329 let no_visit = no_visit(input);
330 let rest: TokenStream = input.parse()?;
331 Ok(AstEnum(if no_visit {
332 vec![]
333 } else {
334 vec![AstItem {
335 ast: syn::parse2(quote! {
336 pub enum #ident #rest
337 })?,
338 features: vec![],
339 }]
340 }))
341 }
342 }
343
344 // A single variant of an ast_enum_of_structs!
345 struct EosVariant {
346 name: Ident,
347 member: Option<Path>,
348 inner: Option<AstItem>,
349 }
350 fn eos_variant(input: ParseStream) -> Result<EosVariant> {
351 input.call(Attribute::parse_outer)?;
352 input.parse::<Token![pub]>()?;
353 let variant: Ident = input.parse()?;
354 let (member, inner) = if input.peek(token::Paren) {
355 let content;
356 parenthesized!(content in input);
357 if content.fork().call(ast_struct_inner).is_ok() {
358 let item = content.call(ast_struct_inner)?;
359 (Some(Path::from(item.ast.ident.clone())), Some(item))
360 } else {
361 let path: Path = content.parse()?;
362 (Some(path), None)
363 }
364 } else {
365 (None, None)
366 };
367 input.parse::<Token![,]>()?;
368 Ok(EosVariant {
369 name: variant,
370 member,
371 inner,
372 })
373 }
374
375 // ast_enum_of_structs! parsing
376 pub struct AstEnumOfStructs(pub Vec<AstItem>);
377 impl Parse for AstEnumOfStructs {
378 fn parse(input: ParseStream) -> Result<Self> {
379 input.call(Attribute::parse_outer)?;
380 input.parse::<Token![pub]>()?;
381 input.parse::<Token![enum]>()?;
382 let ident: Ident = input.parse()?;
383
384 let content;
385 braced!(content in input);
386 let mut variants = Vec::new();
387 while !content.is_empty() {
388 variants.push(content.call(eos_variant)?);
389 }
390
391 if let Some(ident) = input.parse::<Option<Ident>>()? {
392 assert_eq!(ident, "do_not_generate_to_tokens");
393 }
394
395 let enum_item = {
396 let variants = variants.iter().map(|v| {
397 let name = v.name.clone();
398 match v.member {
399 Some(ref member) => quote!(#name(#member)),
400 None => quote!(#name),
401 }
402 });
403 parse_quote! {
404 pub enum #ident {
405 #(#variants),*
406 }
407 }
408 };
409 let mut items = vec![AstItem {
410 ast: enum_item,
411 features: vec![],
412 }];
413 items.extend(variants.into_iter().filter_map(|v| v.inner));
414 Ok(AstEnumOfStructs(items))
415 }
416 }
417
418 pub struct TokenMacro(pub TokenLookup);
419 impl Parse for TokenMacro {
420 fn parse(input: ParseStream) -> Result<Self> {
421 let mut tokens = BTreeMap::new();
422 while !input.is_empty() {
423 let content;
424 parenthesized!(content in input);
425 let token = content.parse::<TokenStream>()?.to_string();
426 input.parse::<Token![=]>()?;
427 input.parse::<Token![>]>()?;
428 let content;
429 braced!(content in input);
430 input.parse::<Token![;]>()?;
431 content.parse::<token::Dollar>()?;
432 let path: Path = content.parse()?;
433 let ty = path.segments.last().unwrap().into_value().ident.to_string();
434 tokens.insert(token, ty.to_string());
435 }
436 Ok(TokenMacro(tokens))
437 }
438 }
439
440 fn parse_feature(input: ParseStream) -> Result<String> {
441 let i: syn::Ident = input.parse()?;
442 assert_eq!(i, "feature");
443
444 input.parse::<Token![=]>()?;
445 let s = input.parse::<syn::LitStr>()?;
446
447 Ok(s.value())
448 }
449
450 impl Parse for types::Features {
451 fn parse(input: ParseStream) -> Result<Self> {
David Tolnay440fe582019-02-15 20:23:14 -0800452 let mut features = BTreeSet::new();
Carl Lerche058ff472019-02-13 16:23:52 -0800453
454 let level_1;
455 parenthesized!(level_1 in input);
456
457 let i: syn::Ident = level_1.fork().parse()?;
458
459 if i == "any" {
460 level_1.parse::<syn::Ident>()?;
461
462 let level_2;
463 parenthesized!(level_2 in level_1);
464
465 while !level_2.is_empty() {
David Tolnay440fe582019-02-15 20:23:14 -0800466 features.insert(parse_feature(&level_2)?);
Carl Lerche058ff472019-02-13 16:23:52 -0800467
468 if !level_2.is_empty() {
469 level_2.parse::<Token![,]>()?;
470 }
471 }
472 } else if i == "feature" {
David Tolnay440fe582019-02-15 20:23:14 -0800473 features.insert(parse_feature(&level_1)?);
Carl Lerche058ff472019-02-13 16:23:52 -0800474 assert!(level_1.is_empty());
475 } else {
476 panic!("{:?}", i);
477 }
478
479 assert!(input.is_empty());
480
David Tolnayfa67ab02019-02-15 20:17:30 -0800481 Ok(types::Features { any: features })
Carl Lerche058ff472019-02-13 16:23:52 -0800482 }
483 }
484}
485
486fn get_features(attrs: &[syn::Attribute], base: &[syn::Attribute]) -> Vec<syn::Attribute> {
487 let mut ret = base.to_owned();
488
489 for attr in attrs {
490 if attr.path.is_ident("cfg") {
491 ret.push(attr.clone());
492 }
493 }
494
495 ret
496}
497
498type Error = Box<::std::error::Error>;
499
500fn load_file<P: AsRef<Path>>(
501 name: P,
502 features: &[syn::Attribute],
503 lookup: &mut ItemLookup,
504) -> Result<(), Error> {
505 let name = name.as_ref();
506 let parent = name.parent().ok_or("no parent path")?;
507
508 let mut f = File::open(name)?;
509 let mut src = String::new();
510 f.read_to_string(&mut src)?;
511
512 // Parse the file
513 let file = syn::parse_file(&src)?;
514
515 // Collect all of the interesting AstItems declared in this file or submodules.
516 'items: for item in file.items {
517 match item {
518 Item::Mod(item) => {
519 // Don't inspect inline modules.
520 if item.content.is_some() {
521 continue;
522 }
523
524 // We don't want to try to load the generated rust files and
525 // parse them, so we ignore them here.
526 for name in IGNORED_MODS {
527 if item.ident == name {
528 continue 'items;
529 }
530 }
531
532 // Lookup any #[cfg()] attributes on the module and add them to
533 // the feature set.
534 //
535 // The derive module is weird because it is built with either
536 // `full` or `derive` but exported only under `derive`.
537 let features = if item.ident == "derive" {
538 vec![parse_quote!(#[cfg(feature = "derive")])]
539 } else {
540 get_features(&item.attrs, features)
541 };
542
543 // Look up the submodule file, and recursively parse it.
544 // XXX: Only handles same-directory .rs file submodules.
545 let path = parent.join(&format!("{}.rs", item.ident));
546 load_file(path, &features, lookup)?;
547 }
548 Item::Macro(item) => {
549 // Lookip any #[cfg()] attributes directly on the macro
550 // invocation, and add them to the feature set.
551 let features = get_features(&item.attrs, features);
552
553 // Try to parse the AstItem declaration out of the item.
554 let tts = &item.mac.tts;
555 let found = if item.mac.path.is_ident("ast_struct") {
556 syn::parse2::<parsing::AstStruct>(quote!(#tts))?.0
557 } else if item.mac.path.is_ident("ast_enum") {
558 syn::parse2::<parsing::AstEnum>(quote!(#tts))?.0
559 } else if item.mac.path.is_ident("ast_enum_of_structs") {
560 syn::parse2::<parsing::AstEnumOfStructs>(quote!(#tts))?.0
561 } else {
562 continue;
563 };
564
565 // Record our features on the parsed AstItems.
566 for mut item in found {
567 item.features.extend(features.clone());
568 lookup.insert(item.ast.ident.clone(), item);
569 }
570 }
571 Item::Struct(item) => {
572 let ident = item.ident;
573 if EXTRA_TYPES.contains(&&ident.to_string()[..]) {
574 lookup.insert(
575 ident.clone(),
576 AstItem {
577 ast: DeriveInput {
578 ident,
579 vis: item.vis,
580 attrs: item.attrs,
581 generics: item.generics,
582 data: Data::Struct(DataStruct {
583 fields: item.fields,
584 struct_token: item.struct_token,
585 semi_token: item.semi_token,
586 }),
587 },
588 features: features.to_owned(),
589 },
590 );
591 }
592 }
593 _ => {}
594 }
595 }
596 Ok(())
597}
598
599fn load_token_file<P: AsRef<Path>>(name: P) -> Result<TokenLookup, Error> {
600 let name = name.as_ref();
601 let mut f = File::open(name)?;
602 let mut src = String::new();
603 f.read_to_string(&mut src)?;
604 let file = syn::parse_file(&src)?;
605 for item in file.items {
606 match item {
607 Item::Macro(item) => {
608 match item.ident {
609 Some(ref i) if i == "Token" => {}
610 _ => continue,
611 }
612 let tts = &item.mac.tts;
613 let tokens = syn::parse2::<parsing::TokenMacro>(quote!(#tts))?.0;
614 return Ok(tokens);
615 }
616 _ => {}
617 }
618 }
619
620 Err("failed to parse Token macro".into())
621}