1use crate::{version, workspace_path}; 2use anyhow::{bail, Result}; 3use indexmap::IndexMap; 4use quote::quote; 5use std::collections::BTreeMap; 6use std::fs; 7use std::path::{Path, PathBuf}; 8use syn::parse::{Error, Parser}; 9use syn::{ 10 parse_quote, Attribute, Data, DataEnum, DataStruct, DeriveInput, Fields, GenericArgument, 11 Ident, Item, PathArguments, TypeMacro, TypePath, TypeTuple, UseTree, Visibility, 12}; 13use syn_codegen as types; 14use thiserror::Error; 15 16const SYN_CRATE_ROOT: &str = "src/lib.rs"; 17const TOKEN_SRC: &str = "src/token.rs"; 18const IGNORED_MODS: &[&str] = &["fold", "visit", "visit_mut"]; 19const EXTRA_TYPES: &[&str] = &["Lifetime"]; 20 21struct 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. 30pub 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 63pub struct AstItem { 64 ast: DeriveInput, 65 features: Vec<Attribute>, 66} 67 68fn 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 95fn 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 116fn 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 133fn 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 196fn 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 219fn is_pub(vis: &Visibility) -> bool { 220 match vis { 221 Visibility::Public(_) => true, 222 _ => false, 223 } 224} 225 226fn 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 235fn 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 244fn 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 260fn 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 276mod 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 288 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. 299 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. 310 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 322 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 330 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 } 351 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 370 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 412 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 430 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 440 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 468 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 482fn clone_features(features: &[Attribute]) -> Vec<Attribute> { 483 features.iter().map(|attr| parse_quote!(#attr)).collect() 484} 485 486fn 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}")] 500struct LoadFileError { 501 path: PathBuf, 502 line: usize, 503 column: usize, 504 error: Error, 505} 506 507fn 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 528fn 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 633fn 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 648fn 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