utility_macros_internals/derive/
readonly.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use syn::{Data, DeriveInput, Field};
4
5use crate::{
6    derive::{
7        container_attributes::{container_attributes, ContainerAttributesData},
8        field_attributes::{field_attributes, FieldAttributesContext, FieldAttributesData},
9    },
10    option::is_option,
11};
12
13pub fn readonly_impl(
14    DeriveInput {
15        attrs,
16        ident: type_ident,
17        data,
18        ..
19    }: DeriveInput,
20) -> TokenStream {
21    let ContainerAttributesData {
22        ident: readonly_ident,
23        derives,
24        rename_all,
25    } = container_attributes("readonly", attrs, format_ident!("Readonly{}", type_ident));
26
27    let field_attr_context = FieldAttributesContext {
28        helper: "readonly",
29        rename_all,
30    };
31
32    let Data::Struct(data) = data else {
33        panic!("Expected struct")
34    };
35
36    let mut static_assertions = Vec::new();
37    let mut struct_body = Vec::new();
38    let mut impl_getters = Vec::new();
39    let mut impl_new_params = Vec::new();
40    let mut impl_new_body = Vec::new();
41    let mut to_readonly_body = Vec::new();
42    let mut to_type_body = Vec::new();
43    let mut partial_eq_conditions = Vec::new();
44    let mut impl_partial_eq = true;
45
46    for field in &data.fields {
47        let Field {
48            ty,
49            ident: type_ident,
50            ..
51        } = field;
52
53        let type_ident = type_ident.clone().expect("Expected ident");
54
55        let FieldAttributesData {
56            ident: readonly_ident,
57            skip,
58        } = field_attributes(&field_attr_context, field);
59
60        if skip {
61            if is_option(ty) {
62                to_type_body.push(quote! {
63                    #type_ident: None,
64                });
65                partial_eq_conditions.push(quote! {
66                    self.#type_ident.is_none()
67                });
68            } else {
69                static_assertions.push(quote! {
70                    ::utility_macros::_um::_sa::assert_impl_all!(#ty: Default);
71                });
72                to_type_body.push(quote! {
73                    #type_ident: Default::default(),
74                });
75                impl_partial_eq = false;
76            }
77
78            continue;
79        }
80
81        static_assertions.push(quote! {
82            ::utility_macros::_um::_sa::assert_impl_all!(#ty: Clone);
83        });
84
85        struct_body.push(quote! {
86            #readonly_ident: #ty,
87        });
88
89        impl_new_params.push(quote! {
90            #readonly_ident: #ty
91        });
92
93        impl_new_body.push(quote! {
94            #readonly_ident
95        });
96
97        impl_getters.push(quote! {
98            pub fn #readonly_ident(&self) -> &#ty {
99                &self.#type_ident
100            }
101        });
102
103        to_readonly_body.push(quote! {
104            #readonly_ident: self.#type_ident.clone(),
105        });
106
107        to_type_body.push(quote! {
108            #type_ident: self.#readonly_ident.clone(),
109        });
110
111        partial_eq_conditions.push(quote! {
112            self.#type_ident == other.#readonly_ident
113        });
114    }
115
116    let derives = if derives.is_empty() {
117        quote! {}
118    } else {
119        quote! {
120            #[derive(#(#derives),*)]
121        }
122    };
123
124    let partial_eq_impl = if impl_partial_eq {
125        quote! {
126            impl PartialEq<#readonly_ident> for #type_ident {
127                fn eq(&self, other: &#readonly_ident) -> bool {
128                    #(#partial_eq_conditions)&& *
129                }
130            }
131        }
132    } else {
133        quote! {
134            impl PartialEq<#readonly_ident> for #type_ident {
135                fn eq(&self, _: &#readonly_ident) -> bool {
136                    panic!("Partial equality can't be implemented for types that have skipped, non-optional fields");
137                }
138            }
139        }
140    };
141
142    quote! {
143        #(#static_assertions)*
144
145        #derives
146        pub struct #readonly_ident {
147            #(#struct_body)*
148        }
149
150        impl #readonly_ident {
151            pub fn new(#(#impl_new_params),*) -> Self {
152                Self {
153                    #(#impl_new_body),*
154                }
155            }
156
157            #(#impl_getters)*
158        }
159
160        impl ::utility_macros::_um::readonly::HasReadonly for #type_ident {
161            type Readonly = #readonly_ident;
162
163            fn readonly(&self) -> Self::Readonly {
164                Self::Readonly {
165                    #(#to_readonly_body)*
166                }
167            }
168        }
169
170        impl ::utility_macros::_um::readonly::Readonly for #readonly_ident {
171            type Type = #type_ident;
172
173            fn type_(&self) -> Self::Type {
174                #type_ident {
175                    #(#to_type_body)*
176                }
177            }
178        }
179
180        impl From<#type_ident> for #readonly_ident {
181            fn from(value: #type_ident) -> Self {
182                ::utility_macros::_um::readonly::HasReadonly::readonly(&value)
183            }
184        }
185
186        impl From<#readonly_ident> for #type_ident {
187            fn from(value: #readonly_ident) -> Self {
188                ::utility_macros::_um::readonly::Readonly::type_(&value)
189            }
190        }
191
192        #partial_eq_impl
193    }
194}