actuate_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::{format_ident, quote, ToTokens};
3use syn::{
4    parse_macro_input, parse_quote, punctuated::Punctuated, token::Comma, Data, DeriveInput,
5    GenericParam, ItemTrait, MetaNameValue, TypeParamBound,
6};
7
8#[proc_macro_derive(Data, attributes(actuate))]
9pub fn derive_data(input: TokenStream) -> TokenStream {
10    let input = parse_macro_input!(input as DeriveInput);
11    let ident = &input.ident;
12
13    let generics = &input.generics;
14
15    let mut cell = None;
16    if let Some(attr) = input
17        .attrs
18        .iter()
19        .find(|attr| attr.path().is_ident("actuate"))
20    {
21        let args: MetaNameValue = attr.parse_args().unwrap();
22        if args.path.get_ident().unwrap() == "path" {
23            let value = args.value.to_token_stream().to_string();
24            cell = Some(format_ident!("{}", &value[1..value.len() - 1]));
25        }
26    }
27    let actuate = cell.unwrap_or(format_ident!("actuate"));
28
29    let generic_params: Punctuated<_, Comma> = generics
30        .params
31        .iter()
32        .map(|param| match param {
33            GenericParam::Lifetime(lifetime_param) => lifetime_param.to_token_stream(),
34            GenericParam::Type(type_param) => {
35                let ident = &type_param.ident;
36
37                let mut bounds = type_param.bounds.clone();
38                bounds.push(parse_quote!(#actuate::data::Data));
39
40                quote! {
41                    #ident: #bounds
42                }
43            }
44            GenericParam::Const(const_param) => const_param.to_token_stream(),
45        })
46        .collect();
47
48    let generic_ty_params: Punctuated<_, Comma> = generics
49        .params
50        .iter()
51        .map(|param| match param {
52            GenericParam::Lifetime(lifetime_param) => lifetime_param.to_token_stream(),
53            GenericParam::Type(type_param) => type_param.ident.to_token_stream(),
54            GenericParam::Const(const_param) => const_param.to_token_stream(),
55        })
56        .collect();
57
58    let Data::Struct(input_struct) = input.data else {
59        todo!()
60    };
61
62    let checks = input_struct.fields.iter().map(|field| {
63        let field_ident = field.ident.as_ref().unwrap();
64        let check_ident = format_ident!("__check_{}_{}", ident, field_ident);
65        quote! {
66           #[doc(hidden)]
67           #[allow(non_snake_case)]
68           fn #check_ident <#generic_params> (t: #ident <#generic_ty_params>) {
69                use #actuate::data::{FieldWrap, DataField, FnField, StaticField};
70
71                (&&FieldWrap(t.#field_ident)).check()
72           }
73        }
74    });
75
76    let gen = quote! {
77        #( #checks )*
78
79        #[doc(hidden)]
80        unsafe impl <#generic_params> #actuate::data::Data for #ident <#generic_ty_params> {}
81    };
82    gen.into()
83}
84
85#[proc_macro_attribute]
86pub fn data(_attrs: TokenStream, input: TokenStream) -> TokenStream {
87    let item = parse_macro_input!(input as ItemTrait);
88
89    let contains_data = item.supertraits.iter().any(|x| {
90        if let TypeParamBound::Trait(trait_bound) = x {
91            if trait_bound.path.is_ident("Data") {
92                return true;
93            }
94        }
95
96        false
97    });
98
99    if !contains_data {
100        return quote! {
101            compile_error!("\
102                Traits used as `Data` must require all implementations to be `Data`. \
103                To fix this, add `Data` as a supertrait to your trait (i.e trait MyTrait: Data {}).\
104            ");
105        }
106        .into();
107    }
108
109    let ident = &item.ident;
110
111    quote! {
112        #item
113
114        unsafe impl actuate::data::Data for Box<dyn #ident + '_> {}
115    }
116    .into()
117}