Skip to main content

apecs_derive_canfetch/
lib.rs

1//! This library is meant to be used to create `CanFetch` derive macros for the
2//! `apecs` library and libraries that might light to encapsulate and re-export
3//! the `apecs` library.
4use quote::quote;
5use syn::{
6    punctuated::Punctuated, token::Comma, Data, DataStruct, DeriveInput, Field, Fields,
7    FieldsNamed, FieldsUnnamed, Ident, Path, Type, WhereClause, WherePredicate,
8};
9
10fn collect_field_types(fields: &Punctuated<Field, Comma>) -> Vec<Type> {
11    fields.iter().map(|x| x.ty.clone()).collect()
12}
13
14fn gen_identifiers(fields: &Punctuated<Field, Comma>) -> Vec<Ident> {
15    fields.iter().map(|x| x.ident.clone().unwrap()).collect()
16}
17
18fn gen_from_body(path: &Path, ast: &Data, name: &Ident) -> (proc_macro2::TokenStream, Vec<Type>) {
19    enum DataType {
20        Struct,
21        Tuple,
22    }
23
24    let (body, fields) = match *ast {
25        Data::Struct(DataStruct {
26            fields: Fields::Named(FieldsNamed { named: ref x, .. }),
27            ..
28        }) => (DataType::Struct, x),
29        Data::Struct(DataStruct {
30            fields: Fields::Unnamed(FieldsUnnamed { unnamed: ref x, .. }),
31            ..
32        }) => (DataType::Tuple, x),
33        _ => panic!("Enums are not supported"),
34    };
35
36    let tys = collect_field_types(fields);
37
38    let fetch_return = match body {
39        DataType::Struct => {
40            let identifiers = gen_identifiers(fields);
41
42            quote! {
43                #name {
44                    #( #identifiers: #path::CanFetch::construct(loan_mngr)? ),*
45                }
46            }
47        }
48        DataType::Tuple => {
49            let count = tys.len();
50            let fetch = vec![quote! { #path::CanFetch::construct(loan_mngr)? }; count];
51
52            quote! {
53                #name ( #( #fetch ),* )
54            }
55        }
56    };
57
58    (fetch_return, tys)
59}
60
61/// Helper to create `CanFetch` derive macros with a configurable module prefix.
62pub fn derive_canfetch(path: Path, input: DeriveInput) -> proc_macro2::TokenStream {
63    let name = input.ident;
64    let (construct_return, tys) = gen_from_body(&path, &input.data, &name);
65    let mut generics = input.generics;
66    {
67        /// Adds a `CanFetch<'lt>` bound on each of the system data types.
68        fn constrain_system_data_types(path: &Path, clause: &mut WhereClause, tys: &[Type]) {
69            for ty in tys.iter() {
70                let where_predicate: WherePredicate = syn::parse_quote!(#ty : #path::CanFetch);
71                clause.predicates.push(where_predicate);
72            }
73        }
74
75        let where_clause = generics.make_where_clause();
76        constrain_system_data_types(&path, where_clause, &tys)
77    }
78
79    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
80
81    let output = quote! {
82        impl #impl_generics #path::CanFetch for #name #ty_generics #where_clause {
83            fn borrows() -> Vec<#path::internal::Borrow> {
84                let mut r = Vec::new();
85                #({
86                    r.extend(<#tys as #path::CanFetch>::borrows());
87                })*
88                r
89            }
90
91            fn construct(loan_mngr: &mut #path::internal::LoanManager) -> anyhow::Result<Self> {
92                Ok(#construct_return)
93            }
94
95            fn plugin() -> #path::Plugin {
96                #path::Plugin::default()
97                    #(.with_plugin(<#tys as #path::CanFetch>::plugin()))*
98            }
99        }
100    };
101
102    output.into()
103}
104
105/// Helper to create `TryDefault` derive macros with a configurable module prefix.
106pub fn derive_trydefault(path: Path, mut input: DeriveInput) -> proc_macro2::TokenStream {
107    let name = input.ident;
108    {input.generics.make_where_clause();}
109    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
110    let where_clause = if let Some(mut clause) = where_clause.map(|c| c.clone()) {
111        let where_predicate: WherePredicate = syn::parse_quote!(#name #ty_generics : Default);
112        clause.predicates.push(where_predicate);
113        clause
114    } else {
115        syn::parse_quote!(where #name #ty_generics: Default)
116    };
117    let output = quote!{
118        impl #impl_generics #path::TryDefault for #name #ty_generics #where_clause {
119            fn try_default() -> Option<Self> {
120                Some(Self::default())
121            }
122        }
123    };
124    output.into()
125}