utility_macros_internals/derive/
partial.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::{as_option, is_option},
11};
12
13pub fn partial_impl(
14    DeriveInput {
15        attrs,
16        ident: type_ident,
17        data,
18        ..
19    }: DeriveInput,
20) -> TokenStream {
21    let ContainerAttributesData {
22        ident: partial_ident,
23        derives,
24        rename_all,
25    } = container_attributes("partial", attrs, format_ident!("Partial{}", type_ident));
26
27    let field_attr_context = FieldAttributesContext {
28        helper: "partial",
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 to_partial_body = Vec::new();
39    let mut to_full_body = Vec::new();
40    let mut impl_partial_eq = true;
41    let mut partial_eq = Vec::new();
42
43    for field in &data.fields {
44        let Field {
45            vis,
46            ty,
47            ident: type_ident,
48            ..
49        } = field;
50
51        let type_ident = type_ident.clone().expect("Expected ident");
52
53        let FieldAttributesData {
54            ident: partial_ident,
55            skip,
56        } = field_attributes(&field_attr_context, field);
57
58        if skip {
59            if is_option(ty) {
60                to_full_body.push(quote! {
61                    #type_ident: None,
62                });
63                partial_eq.push(quote! {
64                    self.#type_ident.is_none()
65                });
66            } else {
67                static_assertions.push(quote! {
68                    ::utility_macros::_um::_sa::assert_impl_all!(#ty: Default);
69                });
70                to_full_body.push(quote! {
71                    #type_ident: Default::default(),
72                });
73                impl_partial_eq = false;
74            }
75            continue;
76        }
77
78        let opt_ty = as_option(ty);
79        struct_body.push(quote! {
80            #vis #partial_ident: #opt_ty,
81        });
82
83        static_assertions.push(quote! {
84            ::utility_macros::_um::_sa::assert_impl_all!(#opt_ty: Clone);
85        });
86
87        if is_option(ty) {
88            to_partial_body.push(quote! {
89                #partial_ident: self.#type_ident.clone(),
90            });
91            to_full_body.push(quote! {
92                #type_ident: self.#partial_ident.clone(),
93            });
94            partial_eq.push(quote! {
95                self.#type_ident == other.#partial_ident
96            });
97        } else {
98            to_partial_body.push(quote! {
99                #partial_ident: Some(self.#type_ident.clone()),
100            });
101            to_full_body.push(quote! {
102                #type_ident: self.#partial_ident.clone().ok_or_else(|| ::utility_macros::_um::error::Error::MissingField(stringify!(#partial_ident)))?,
103            });
104            partial_eq.push(quote! {
105                other.#partial_ident.clone().map_or(false, |val| self.#type_ident == val)
106            });
107        }
108    }
109
110    let derives = if derives.is_empty() {
111        quote! {}
112    } else {
113        quote! {
114            #[derive(#(#derives),*)]
115        }
116    };
117
118    let partial_eq_impl = if impl_partial_eq {
119        quote! {
120            impl PartialEq<#partial_ident> for #type_ident {
121                fn eq(&self, other: &#partial_ident) -> bool {
122                    #(#partial_eq)&& *
123                }
124            }
125        }
126    } else {
127        quote! {
128            impl PartialEq<#partial_ident> for #type_ident {
129                fn eq(&self, _: &#partial_ident) -> bool {
130                    panic!("Partial equality can't be implemented for types that have skipped, non-optional fields");
131                }
132            }
133        }
134    };
135
136    quote! {
137        #(#static_assertions)*
138
139        #derives
140        pub struct #partial_ident {
141            #(#struct_body)*
142        }
143
144        impl ::utility_macros::_um::partial::HasPartial for #type_ident {
145            type Partial = #partial_ident;
146
147            fn partial(&self) -> Self::Partial {
148                Self::Partial {
149                    #(#to_partial_body)*
150                }
151            }
152        }
153
154        impl ::utility_macros::_um::partial::Partial for #partial_ident {
155            type Type = #type_ident;
156
157            fn type_(&self) -> ::utility_macros::_um::error::Result<Self::Type> {
158                Ok(#type_ident {
159                    #(#to_full_body)*
160                })
161            }
162        }
163
164        impl From<#type_ident> for #partial_ident {
165            fn from(type_: #type_ident) -> Self {
166                ::utility_macros::_um::partial::HasPartial::partial(&type_)
167            }
168        }
169
170        impl TryFrom<#partial_ident> for #type_ident {
171            type Error = ::utility_macros::_um::error::Error;
172
173            fn try_from(partial: #partial_ident) -> ::utility_macros::_um::error::Result<Self> {
174                ::utility_macros::_um::partial::Partial::type_(&partial)
175            }
176        }
177       #partial_eq_impl
178    }
179}