xref: /third_party/rust/crates/syn/codegen/src/parse.rs (revision fad3a1d3)
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