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}