1 use crate::{version, workspace_path};
2 use anyhow::{bail, Result};
3 use indexmap::IndexMap;
4 use quote::quote;
5 use std::collections::BTreeMap;
6 use std::fs;
7 use std::path::{Path, PathBuf};
8 use syn::parse::{Error, Parser};
9 use syn::{
10     parse_quote, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, GenericArgument,
11     Ident, Item, PathArguments, TypeMacro, TypePath, TypeTuple, UseTree, Visibility,
12 };
13 use syn_codegen as types;
14 use thiserror::Error;
15 
16 const SYN_CRATE_ROOT: &str = "src/lib.rs";
17 const TOKEN_SRC: &str = "src/token.rs";
18 const IGNORED_MODS: &[&str] = &["fold", "visit", "visit_mut"];
19 const EXTRA_TYPES: &[&str] = &["Lifetime"];
20 
21 struct Lookup {
22     items: BTreeMap<Ident, AstItem>,
23     // "+" => "Add"
24     tokens: BTreeMap<String, String>,
25     // "PatLit" => "ExprLit"
26     aliases: BTreeMap<Ident, Ident>,
27 }
28 
29 /// Parse the contents of `src` and return a list of AST types.
parsenull30 pub fn parse() -> Result<types::Definitions> {
31     let tokens = load_token_file(TOKEN_SRC)?;
32 
33     let mut lookup = Lookup {
34         items: BTreeMap::new(),
35         tokens,
36         aliases: BTreeMap::new(),
37     };
38 
39     load_file(SYN_CRATE_ROOT, &[], &mut lookup)?;
40 
41     let version = version::get()?;
42 
43     let types = lookup
44         .items
45         .values()
46         .map(|item| introspect_item(item, &lookup))
47         .collect();
48 
49     let tokens = lookup
50         .tokens
51         .into_iter()
52         .map(|(name, ty)| (ty, name))
53         .collect();
54 
55     Ok(types::Definitions {
56         version,
57         types,
58         tokens,
59     })
60 }
61 
62 /// Data extracted from syn source
63 pub struct AstItem {
64     ast: DeriveInput,
65     features: Vec<Attribute>,
66 }
67 
introspect_itemnull68 fn introspect_item(item: &AstItem, lookup: &Lookup) -> types::Node {
69     let features = introspect_features(&item.features);
70 
71     match &item.ast.data {
72         Data::Enum(data) => types::Node {
73             ident: item.ast.ident.to_string(),
74             features,
75             data: types::Data::Enum(introspect_enum(data, lookup)),
76             exhaustive: !(is_non_exhaustive(&item.ast.attrs)
77                 || data.variants.iter().any(|v| is_doc_hidden(&v.attrs))),
78         },
79         Data::Struct(data) => types::Node {
80             ident: item.ast.ident.to_string(),
81             features,
82             data: {
83                 if data.fields.iter().all(|f| is_pub(&f.vis)) {
84                     types::Data::Struct(introspect_struct(data, lookup))
85                 } else {
86                     types::Data::Private
87                 }
88             },
89             exhaustive: true,
90         },
91         Data::Union(..) => panic!("Union not supported"),
92     }
93 }
94 
introspect_enumnull95 fn introspect_enum(item: &DataEnum, lookup: &Lookup) -> types::Variants {
96     item.variants
97         .iter()
98         .filter_map(|variant| {
99             if is_doc_hidden(&variant.attrs) {
100                 return None;
101             }
102             let fields = match &variant.fields {
103                 Fields::Unnamed(fields) => fields
104                     .unnamed
105                     .iter()
106                     .map(|field| introspect_type(&field.ty, lookup))
107                     .collect(),
108                 Fields::Unit => vec![],
109                 Fields::Named(_) => panic!("Enum representation not supported"),
110             };
111             Some((variant.ident.to_string(), fields))
112         })
113         .collect()
114 }
115 
introspect_structnull116 fn introspect_struct(item: &DataStruct, lookup: &Lookup) -> types::Fields {
117     match &item.fields {
118         Fields::Named(fields) => fields
119             .named
120             .iter()
121             .map(|field| {
122                 (
123                     field.ident.as_ref().unwrap().to_string(),
124                     introspect_type(&field.ty, lookup),
125                 )
126             })
127             .collect(),
128         Fields::Unit => IndexMap::new(),
129         Fields::Unnamed(_) => panic!("Struct representation not supported"),
130     }
131 }
132 
introspect_typenull133 fn introspect_type(item: &syn::Type, lookup: &Lookup) -> types::Type {
134     match item {
135         syn::Type::Path(TypePath { qself: None, path }) => {
136             let last = path.segments.last().unwrap();
137             let string = last.ident.to_string();
138 
139             match string.as_str() {
140                 "Option" => {
141                     let nested = introspect_type(first_arg(&last.arguments), lookup);
142                     types::Type::Option(Box::new(nested))
143                 }
144                 "Punctuated" => {
145                     let nested = introspect_type(first_arg(&last.arguments), lookup);
146                     let types::Type::Token(punct) =
147                         introspect_type(last_arg(&last.arguments), lookup)
148                     else {
149                         panic!()
150                     };
151                     types::Type::Punctuated(types::Punctuated {
152                         element: Box::new(nested),
153                         punct,
154                     })
155                 }
156                 "Vec" => {
157                     let nested = introspect_type(first_arg(&last.arguments), lookup);
158                     types::Type::Vec(Box::new(nested))
159                 }
160                 "Box" => {
161                     let nested = introspect_type(first_arg(&last.arguments), lookup);
162                     types::Type::Box(Box::new(nested))
163                 }
164                 "Brace" | "Bracket" | "Paren" | "Group" => types::Type::Group(string),
165                 "TokenStream" | "Literal" | "Ident" | "Span" => types::Type::Ext(string),
166                 "String" | "u32" | "usize" | "bool" => types::Type::Std(string),
167                 _ => {
168                     let mut resolved = &last.ident;
169                     while let Some(alias) = lookup.aliases.get(resolved) {
170                         resolved = alias;
171                     }
172                     if lookup.items.get(resolved).is_some() {
173                         types::Type::Syn(resolved.to_string())
174                     } else {
175                         unimplemented!("{}", resolved);
176                     }
177                 }
178             }
179         }
180         syn::Type::Tuple(TypeTuple { elems, .. }) => {
181             let tys = elems.iter().map(|ty| introspect_type(ty, lookup)).collect();
182             types::Type::Tuple(tys)
183         }
184         syn::Type::Macro(TypeMacro { mac })
185             if mac.path.segments.last().unwrap().ident == "Token" =>
186         {
187             let content = mac.tokens.to_string();
188             let ty = lookup.tokens.get(&content).unwrap().to_string();
189 
190             types::Type::Token(ty)
191         }
192         _ => panic!("{}", quote!(#item).to_string()),
193     }
194 }
195 
introspect_featuresnull196 fn introspect_features(attrs: &[Attribute]) -> types::Features {
197     let mut ret = types::Features::default();
198 
199     for attr in attrs {
200         if !attr.path().is_ident("cfg") {
201             continue;
202         }
203 
204         let features = attr.parse_args_with(parsing::parse_features).unwrap();
205 
206         if ret.any.is_empty() {
207             ret = features;
208         } else if ret.any.len() < features.any.len() {
209             assert!(ret.any.iter().all(|f| features.any.contains(f)));
210         } else {
211             assert!(features.any.iter().all(|f| ret.any.contains(f)));
212             ret = features;
213         }
214     }
215 
216     ret
217 }
218 
is_pubnull219 fn is_pub(vis: &Visibility) -> bool {
220     match vis {
221         Visibility::Public(_) => true,
222         _ => false,
223     }
224 }
225 
is_non_exhaustivenull226 fn is_non_exhaustive(attrs: &[Attribute]) -> bool {
227     for attr in attrs {
228         if attr.path().is_ident("non_exhaustive") {
229             return true;
230         }
231     }
232     false
233 }
234 
is_doc_hiddennull235 fn is_doc_hidden(attrs: &[Attribute]) -> bool {
236     for attr in attrs {
237         if attr.path().is_ident("doc") && attr.parse_args::<parsing::kw::hidden>().is_ok() {
238             return true;
239         }
240     }
241     false
242 }
243 
first_argnull244 fn first_arg(params: &PathArguments) -> &syn::Type {
245     let data = match params {
246         PathArguments::AngleBracketed(data) => data,
247         _ => panic!("Expected at least 1 type argument here"),
248     };
249 
250     match data
251         .args
252         .first()
253         .expect("Expected at least 1 type argument here")
254     {
255         GenericArgument::Type(ty) => ty,
256         _ => panic!("Expected at least 1 type argument here"),
257     }
258 }
259 
last_argnull260 fn last_arg(params: &PathArguments) -> &syn::Type {
261     let data = match params {
262         PathArguments::AngleBracketed(data) => data,
263         _ => panic!("Expected at least 1 type argument here"),
264     };
265 
266     match data
267         .args
268         .last()
269         .expect("Expected at least 1 type argument here")
270     {
271         GenericArgument::Type(ty) => ty,
272         _ => panic!("Expected at least 1 type argument here"),
273     }
274 }
275 
276 mod parsing {
277     use super::AstItem;
278     use proc_macro2::TokenStream;
279     use quote::quote;
280     use std::collections::{BTreeMap, BTreeSet};
281     use syn::parse::{ParseStream, Result};
282     use syn::{
283         braced, bracketed, parenthesized, parse_quote, token, Attribute, Expr, Ident, Lit, LitStr,
284         Path, Token,
285     };
286     use syn_codegen as types;
287 
peek_tagnull288     fn peek_tag(input: ParseStream, tag: &str) -> bool {
289         let ahead = input.fork();
290         ahead.parse::<Token![#]>().is_ok()
291             && ahead
292                 .parse::<Ident>()
293                 .map(|ident| ident == tag)
294                 .unwrap_or(false)
295     }
296 
297     // Parses #full - returns #[cfg(feature = "full")] if it is present, and
298     // nothing otherwise.
fullnull299     fn full(input: ParseStream) -> Vec<Attribute> {
300         if peek_tag(input, "full") {
301             input.parse::<Token![#]>().unwrap();
302             input.parse::<Ident>().unwrap();
303             vec![parse_quote!(#[cfg(feature = "full")])]
304         } else {
305             vec![]
306         }
307     }
308 
309     // Parses a simple AstStruct without the `pub struct` prefix.
ast_struct_innernull310     fn ast_struct_inner(input: ParseStream) -> Result<AstItem> {
311         let ident: Ident = input.parse()?;
312         let features = full(input);
313         let rest: TokenStream = input.parse()?;
314         Ok(AstItem {
315             ast: syn::parse2(quote! {
316                 pub struct #ident #rest
317             })?,
318             features,
319         })
320     }
321 
ast_structnull322     pub fn ast_struct(input: ParseStream) -> Result<AstItem> {
323         input.call(Attribute::parse_outer)?;
324         input.parse::<Token![pub]>()?;
325         input.parse::<Token![struct]>()?;
326         let res = input.call(ast_struct_inner)?;
327         Ok(res)
328     }
329 
ast_enumnull330     pub fn ast_enum(input: ParseStream) -> Result<AstItem> {
331         let attrs = input.call(Attribute::parse_outer)?;
332         input.parse::<Token![pub]>()?;
333         input.parse::<Token![enum]>()?;
334         let ident: Ident = input.parse()?;
335         let rest: TokenStream = input.parse()?;
336         Ok(AstItem {
337             ast: syn::parse2(quote! {
338                 #(#attrs)*
339                 pub enum #ident #rest
340             })?,
341             features: vec![],
342         })
343     }
344 
345     // A single variant of an ast_enum_of_structs!
346     struct EosVariant {
347         attrs: Vec<Attribute>,
348         name: Ident,
349         member: Option<Path>,
350     }
eos_variantnull351     fn eos_variant(input: ParseStream) -> Result<EosVariant> {
352         let attrs = input.call(Attribute::parse_outer)?;
353         let variant: Ident = input.parse()?;
354         let member = if input.peek(token::Paren) {
355             let content;
356             parenthesized!(content in input);
357             let path: Path = content.parse()?;
358             Some(path)
359         } else {
360             None
361         };
362         input.parse::<Token![,]>()?;
363         Ok(EosVariant {
364             attrs,
365             name: variant,
366             member,
367         })
368     }
369 
ast_enum_of_structsnull370     pub fn ast_enum_of_structs(input: ParseStream) -> Result<AstItem> {
371         let attrs = input.call(Attribute::parse_outer)?;
372         input.parse::<Token![pub]>()?;
373         input.parse::<Token![enum]>()?;
374         let ident: Ident = input.parse()?;
375 
376         let content;
377         braced!(content in input);
378         let mut variants = Vec::new();
379         while !content.is_empty() {
380             variants.push(content.call(eos_variant)?);
381         }
382 
383         let enum_item = {
384             let variants = variants.iter().map(|v| {
385                 let attrs = &v.attrs;
386                 let name = &v.name;
387                 if let Some(member) = &v.member {
388                     quote!(#(#attrs)* #name(#member))
389                 } else {
390                     quote!(#(#attrs)* #name)
391                 }
392             });
393             parse_quote! {
394                 #(#attrs)*
395                 pub enum #ident {
396                     #(#variants),*
397                 }
398             }
399         };
400         Ok(AstItem {
401             ast: enum_item,
402             features: vec![],
403         })
404     }
405 
406     pub mod kw {
407         syn::custom_keyword!(hidden);
408         syn::custom_keyword!(macro_rules);
409         syn::custom_keyword!(Token);
410     }
411 
parse_token_macronull412     pub fn parse_token_macro(input: ParseStream) -> Result<BTreeMap<String, String>> {
413         let mut tokens = BTreeMap::new();
414         while !input.is_empty() {
415             let pattern;
416             bracketed!(pattern in input);
417             let token = pattern.parse::<TokenStream>()?.to_string();
418             input.parse::<Token![=>]>()?;
419             let expansion;
420             braced!(expansion in input);
421             input.parse::<Token![;]>()?;
422             expansion.parse::<Token![$]>()?;
423             let path: Path = expansion.parse()?;
424             let ty = path.segments.last().unwrap().ident.to_string();
425             tokens.insert(token, ty.to_string());
426         }
427         Ok(tokens)
428     }
429 
parse_featurenull430     fn parse_feature(input: ParseStream) -> Result<String> {
431         let i: Ident = input.parse()?;
432         assert_eq!(i, "feature");
433 
434         input.parse::<Token![=]>()?;
435         let s = input.parse::<LitStr>()?;
436 
437         Ok(s.value())
438     }
439 
parse_featuresnull440     pub fn parse_features(input: ParseStream) -> Result<types::Features> {
441         let mut features = BTreeSet::new();
442 
443         let i: Ident = input.fork().parse()?;
444 
445         if i == "any" {
446             input.parse::<Ident>()?;
447 
448             let nested;
449             parenthesized!(nested in input);
450 
451             while !nested.is_empty() {
452                 features.insert(parse_feature(&nested)?);
453 
454                 if !nested.is_empty() {
455                     nested.parse::<Token![,]>()?;
456                 }
457             }
458         } else if i == "feature" {
459             features.insert(parse_feature(input)?);
460             assert!(input.is_empty());
461         } else {
462             panic!("{:?}", i);
463         }
464 
465         Ok(types::Features { any: features })
466     }
467 
path_attrnull468     pub fn path_attr(attrs: &[Attribute]) -> Result<Option<&LitStr>> {
469         for attr in attrs {
470             if attr.path().is_ident("path") {
471                 if let Expr::Lit(expr) = &attr.meta.require_name_value()?.value {
472                     if let Lit::Str(lit) = &expr.lit {
473                         return Ok(Some(lit));
474                     }
475                 }
476             }
477         }
478         Ok(None)
479     }
480 }
481 
clone_featuresnull482 fn clone_features(features: &[Attribute]) -> Vec<Attribute> {
483     features.iter().map(|attr| parse_quote!(#attr)).collect()
484 }
485 
get_featuresnull486 fn get_features(attrs: &[Attribute], base: &[Attribute]) -> Vec<Attribute> {
487     let mut ret = clone_features(base);
488 
489     for attr in attrs {
490         if attr.path().is_ident("cfg") {
491             ret.push(parse_quote!(#attr));
492         }
493     }
494 
495     ret
496 }
497 
498 #[derive(Error, Debug)]
499 #[error("{path}:{line}:{column}: {error}")]
500 struct LoadFileError {
501     path: PathBuf,
502     line: usize,
503     column: usize,
504     error: Error,
505 }
506 
load_filenull507 fn load_file(
508     relative_to_workspace_root: impl AsRef<Path>,
509     features: &[Attribute],
510     lookup: &mut Lookup,
511 ) -> Result<()> {
512     let error = match do_load_file(&relative_to_workspace_root, features, lookup).err() {
513         None => return Ok(()),
514         Some(error) => error,
515     };
516 
517     let error = error.downcast::<Error>()?;
518     let span = error.span().start();
519 
520     bail!(LoadFileError {
521         path: relative_to_workspace_root.as_ref().to_owned(),
522         line: span.line,
523         column: span.column + 1,
524         error,
525     })
526 }
527 
do_load_filenull528 fn do_load_file(
529     relative_to_workspace_root: impl AsRef<Path>,
530     features: &[Attribute],
531     lookup: &mut Lookup,
532 ) -> Result<()> {
533     let relative_to_workspace_root = relative_to_workspace_root.as_ref();
534     let parent = relative_to_workspace_root.parent().expect("no parent path");
535 
536     // Parse the file
537     let src = fs::read_to_string(workspace_path::get(relative_to_workspace_root))?;
538     let file = syn::parse_file(&src)?;
539 
540     // Collect all of the interesting AstItems declared in this file or submodules.
541     'items: for item in file.items {
542         match item {
543             Item::Mod(item) => {
544                 // Don't inspect inline modules.
545                 if item.content.is_some() {
546                     continue;
547                 }
548 
549                 // We don't want to try to load the generated rust files and
550                 // parse them, so we ignore them here.
551                 for name in IGNORED_MODS {
552                     if item.ident == name {
553                         continue 'items;
554                     }
555                 }
556 
557                 // Lookup any #[cfg()] attributes on the module and add them to
558                 // the feature set.
559                 //
560                 // The derive module is weird because it is built with either
561                 // `full` or `derive` but exported only under `derive`.
562                 let features = if item.ident == "derive" {
563                     vec![parse_quote!(#[cfg(feature = "derive")])]
564                 } else {
565                     get_features(&item.attrs, features)
566                 };
567 
568                 // Look up the submodule file, and recursively parse it.
569                 // Only handles same-directory .rs file submodules for now.
570                 let filename = if let Some(filename) = parsing::path_attr(&item.attrs)? {
571                     filename.value()
572                 } else {
573                     format!("{}.rs", item.ident)
574                 };
575                 let path = parent.join(filename);
576                 load_file(path, &features, lookup)?;
577             }
578             Item::Macro(item) => {
579                 // Lookip any #[cfg()] attributes directly on the macro
580                 // invocation, and add them to the feature set.
581                 let features = get_features(&item.attrs, features);
582 
583                 // Try to parse the AstItem declaration out of the item.
584                 let tts = item.mac.tokens.clone();
585                 let mut found = if item.mac.path.is_ident("ast_struct") {
586                     parsing::ast_struct.parse2(tts)
587                 } else if item.mac.path.is_ident("ast_enum") {
588                     parsing::ast_enum.parse2(tts)
589                 } else if item.mac.path.is_ident("ast_enum_of_structs") {
590                     parsing::ast_enum_of_structs.parse2(tts)
591                 } else {
592                     continue;
593                 }?;
594 
595                 // Record our features on the parsed AstItems.
596                 found.features.extend(clone_features(&features));
597                 lookup.items.insert(found.ast.ident.clone(), found);
598             }
599             Item::Struct(item) => {
600                 let ident = item.ident;
601                 if EXTRA_TYPES.contains(&&ident.to_string()[..]) {
602                     lookup.items.insert(
603                         ident.clone(),
604                         AstItem {
605                             ast: DeriveInput {
606                                 ident,
607                                 vis: item.vis,
608                                 attrs: item.attrs,
609                                 generics: item.generics,
610                                 data: Data::Struct(DataStruct {
611                                     fields: item.fields,
612                                     struct_token: item.struct_token,
613                                     semi_token: item.semi_token,
614                                 }),
615                             },
616                             features: clone_features(features),
617                         },
618                     );
619                 }
620             }
621             Item::Use(item)
622                 if relative_to_workspace_root == Path::new(SYN_CRATE_ROOT)
623                     && matches!(item.vis, Visibility::Public(_)) =>
624             {
625                 load_aliases(item.tree, lookup);
626             }
627             _ => {}
628         }
629     }
630     Ok(())
631 }
632 
load_aliasesnull633 fn load_aliases(use_tree: UseTree, lookup: &mut Lookup) {
634     match use_tree {
635         UseTree::Path(use_tree) => load_aliases(*use_tree.tree, lookup),
636         UseTree::Rename(use_tree) => {
637             lookup.aliases.insert(use_tree.rename, use_tree.ident);
638         }
639         UseTree::Group(use_tree) => {
640             for use_tree in use_tree.items {
641                 load_aliases(use_tree, lookup);
642             }
643         }
644         UseTree::Name(_) | UseTree::Glob(_) => {}
645     }
646 }
647 
load_token_filenull648 fn load_token_file(
649     relative_to_workspace_root: impl AsRef<Path>,
650 ) -> Result<BTreeMap<String, String>> {
651     let path = workspace_path::get(relative_to_workspace_root);
652     let src = fs::read_to_string(path)?;
653     let file = syn::parse_file(&src)?;
654     for item in file.items {
655         if let Item::Macro(item) = item {
656             match item.ident {
657                 Some(i) if i == "Token" => {}
658                 _ => continue,
659             }
660             let tokens = item.mac.parse_body_with(parsing::parse_token_macro)?;
661             return Ok(tokens);
662         }
663     }
664 
665     panic!("failed to parse Token macro")
666 }
667