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