moongraph_macros_syntax/
lib.rs

1//! Utility functions for writing `moongraph` macros.
2use quote::quote;
3use syn::{
4    punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field, Fields,
5    FieldsNamed, FieldsUnnamed, Ident, Type, WhereClause, WherePredicate,
6};
7
8fn collect_field_types(fields: &Punctuated<Field, Comma>) -> Vec<Type> {
9    fields.iter().map(|x| x.ty.clone()).collect()
10}
11
12fn gen_identifiers(fields: &Punctuated<Field, Comma>) -> Vec<Ident> {
13    fields.iter().map(|x| x.ident.clone().unwrap()).collect()
14}
15
16enum DataType {
17    Struct,
18    Tuple,
19}
20
21fn gen_edges_body(
22    ast: &Data,
23    name: &Ident,
24    path: &syn::Path,
25) -> (proc_macro2::TokenStream, Vec<Type>) {
26    let (body, fields) = match *ast {
27        Data::Struct(DataStruct {
28            fields: Fields::Named(FieldsNamed { named: ref x, .. }),
29            ..
30        }) => (DataType::Struct, x),
31        Data::Struct(DataStruct {
32            fields: Fields::Unnamed(FieldsUnnamed { unnamed: ref x, .. }),
33            ..
34        }) => (DataType::Tuple, x),
35        _ => panic!("Enums are not supported"),
36    };
37
38    let tys = collect_field_types(fields);
39
40    let fetch_return = match body {
41        DataType::Struct => {
42            let identifiers = gen_identifiers(fields);
43
44            quote! {
45                #name {
46                    #( #identifiers: #path::Edges::construct(resources)? ),*
47                }
48            }
49        }
50        DataType::Tuple => {
51            let count = tys.len();
52            let fetch = vec![quote! { #path::Edges::construct(resources)? }; count];
53
54            quote! {
55                #name ( #( #fetch ),* )
56            }
57        }
58    };
59
60    (fetch_return, tys)
61}
62
63/// Find the 'path' attribute in an `Edges` derive macro.
64///
65/// ```ignore
66/// #[moongraph(crate = apecs)]
67///   ^^^^^^^^^         ^^^^^
68///   |                 |
69///   outer_attribute   returned path
70/// ```
71pub fn find_path(
72    outer_attribute: &str,
73    input: &DeriveInput,
74) -> Result<Option<syn::Path>, syn::Error> {
75    let mut path = None;
76    for att in input.attrs.iter() {
77        if att.path().is_ident(outer_attribute) {
78            att.parse_nested_meta(|meta| {
79                if meta.path.is_ident("crate") {
80                    let value = meta.value()?;
81                    let parsed_path: syn::Path = value.parse()?;
82                    path = Some(parsed_path);
83                    Ok(())
84                } else {
85                    Err(meta.error(""))
86                }
87            })?;
88            break;
89        }
90    }
91    Ok(path)
92}
93
94/// Derive `#path::Edges`.
95///
96/// Useful for re-exporting `moongraph::Edges` and the derive macro.
97pub fn derive_edges(input: DeriveInput, path: syn::Path) -> proc_macro2::TokenStream {
98    let name = input.ident;
99    let (construct_return, tys) = gen_edges_body(&input.data, &name, &path);
100    let mut generics = input.generics;
101    {
102        /// Adds a `Edges` bound on each of the system data types.
103        fn constrain_system_data_types(clause: &mut WhereClause, tys: &[Type], path: &syn::Path) {
104            for ty in tys.iter() {
105                let where_predicate: WherePredicate = syn::parse_quote!(#ty : #path::Edges);
106                clause.predicates.push(where_predicate);
107            }
108        }
109
110        let where_clause = generics.make_where_clause();
111        constrain_system_data_types(where_clause, &tys, &path)
112    }
113
114    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
115
116    let output = quote! {
117        impl #impl_generics #path::Edges for #name #ty_generics #where_clause {
118            fn reads() -> Vec<#path::TypeKey> {
119                let mut r = Vec::new();
120                #({
121                    r.extend(<#tys as #path::Edges>::reads());
122                })*
123                r
124            }
125
126            fn writes() -> Vec<#path::TypeKey> {
127                let mut r = Vec::new();
128                #({
129                    r.extend(<#tys as #path::Edges>::writes());
130                })*
131                r
132            }
133
134            fn moves() -> Vec<#path::TypeKey> {
135                let mut r = Vec::new();
136                #({
137                    r.extend(<#tys as #path::Edges>::moves());
138                })*
139                r
140            }
141
142            fn construct(resources: &mut #path::TypeMap) -> Result<Self, #path::GraphError> {
143                Ok(#construct_return)
144            }
145        }
146    };
147
148    output.into()
149}