utility_macros_internals/derive/
readonly.rs1use 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}