1use proc_macro::TokenStream;
2use quote::{format_ident, quote};
3use syn::{Data, DataEnum, DataStruct, DeriveInput, Field, Fields, parse_quote};
4
5pub fn partial_clone(input: TokenStream) -> TokenStream {
6    let input: DeriveInput = syn::parse(input).expect("Unable to parse input");
7
8    let type_ident = &input.ident;
9    let body = match &input.data {
10        Data::Struct(data) => partial_clone_struct_body(data),
11        Data::Enum(data) => partial_clone_enum_body(data),
12        Data::Union(_) => panic!("PartialClone does not support unions; {type_ident} is a union"),
13    };
14
15    let mut generics = input.generics.clone();
16    for type_param in generics.type_params_mut() {
17        type_param.bounds.push(parse_quote!(::jgenesis_common::frontend::PartialClone));
18    }
19    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
20
21    let expanded = quote! {
22        impl #impl_generics ::jgenesis_common::frontend::PartialClone for #type_ident #type_generics #where_clause {
23            fn partial_clone(&self) -> Self {
24                #body
25            }
26        }
27    };
28
29    expanded.into()
30}
31
32fn partial_clone_struct_body(data: &DataStruct) -> proc_macro2::TokenStream {
33    match &data.fields {
34        Fields::Unit => quote! { Self },
35        Fields::Unnamed(fields) => {
36            let constructor_fields: Vec<_> = fields
37                .unnamed
38                .iter()
39                .enumerate()
40                .map(|(i, field)| {
41                    let i = syn::Index::from(i);
42                    match parse_partial_clone_attr(field) {
43                        PartialCloneAttr::None => quote! {
44                            ::std::clone::Clone::clone(&self.#i)
45                        },
46                        PartialCloneAttr::PartialClone => quote! {
47                            ::jgenesis_common::frontend::PartialClone::partial_clone(&self.#i)
48                        },
49                        PartialCloneAttr::Default => quote! {
50                            ::std::default::Default::default()
51                        },
52                    }
53                })
54                .collect();
55
56            quote! {
57                Self(#(#constructor_fields,)*)
58            }
59        }
60        Fields::Named(fields) => {
61            let constructor_fields: Vec<_> = fields
62                .named
63                .iter()
64                .map(|field| {
65                    let field_ident =
66                        field.ident.as_ref().expect("Nested inside Fields::Named match arm");
67                    match parse_partial_clone_attr(field) {
68                        PartialCloneAttr::None => quote! {
69                            #field_ident: ::std::clone::Clone::clone(&self.#field_ident)
70                        },
71                        PartialCloneAttr::PartialClone => quote! {
72                            #field_ident: ::jgenesis_common::frontend::PartialClone::partial_clone(&self.#field_ident)
73                        },
74                        PartialCloneAttr::Default => quote! {
75                            #field_ident: ::std::default::Default::default()
76                        },
77                    }
78                })
79                .collect();
80
81            quote! {
82                Self {
83                    #(#constructor_fields,)*
84                }
85            }
86        }
87    }
88}
89
90fn partial_clone_enum_body(data: &DataEnum) -> proc_macro2::TokenStream {
91    let match_arms: Vec<_> = data.variants.iter().map(|variant| {
92        let variant_ident = &variant.ident;
93        match &variant.fields {
94            Fields::Unit => quote! { Self::#variant_ident => Self::#variant_ident },
95            Fields::Unnamed(fields) => {
96                let (field_idents, field_constructors): (Vec<_>, Vec<_>) = fields.unnamed.iter().enumerate().map(|(i, field)| {
97                    let partial_clone_attr = parse_partial_clone_attr(field);
98
99                    let field_ident = match partial_clone_attr {
100                        PartialCloneAttr::Default => format_ident!("_"),
101                        _ => format_ident!("t{i}")
102                    };
103
104                    let field_constructor = match partial_clone_attr {
105                        PartialCloneAttr::None => quote! {
106                            ::std::clone::Clone::clone(#field_ident)
107                        },
108                        PartialCloneAttr::PartialClone => quote! {
109                            ::jgenesis_common::frontend::PartialClone::partial_clone(#field_ident)
110                        },
111                        PartialCloneAttr::Default => quote! {
112                            ::std::default::Default::default()
113                        },
114                    };
115
116                    (field_ident, field_constructor)
117                }).unzip();
118
119                quote! {
120                    Self::#variant_ident(#(#field_idents,)*) => Self::#variant_ident(#(#field_constructors,)*)
121                }
122            }
123            Fields::Named(fields) => {
124                let (field_bindings, field_constructors): (Vec<_>, Vec<_>) = fields.named.iter().map(|field| {
125                    let partial_clone_attr = parse_partial_clone_attr(field);
126
127                    let field_ident = &field.ident;
128
129                    let field_binding = match partial_clone_attr {
130                        PartialCloneAttr::Default => quote! { #field_ident: _ },
131                        _ => quote! { #field_ident }
132                    };
133
134                    let field_constructor = match partial_clone_attr {
135                        PartialCloneAttr::None => quote! {
136                            #field_ident: ::std::clone::Clone::clone(#field_ident)
137                        },
138                        PartialCloneAttr::PartialClone => quote! {
139                            #field_ident: ::jgenesis_common::frontend::PartialClone::partial_clone(#field_ident)
140                        },
141                        PartialCloneAttr::Default => quote! {
142                            #field_ident: ::std::default::Default::default()
143                        },
144                    };
145
146                    (field_binding, field_constructor)
147                }).unzip();
148
149                quote! {
150                    Self::#variant_ident { #(#field_bindings,)* } => Self::#variant_ident { #(#field_constructors,)* }
151                }
152            }
153        }
154    }).collect();
155
156    quote! {
157        match self {
158            #(#match_arms,)*
159        }
160    }
161}
162
163enum PartialCloneAttr {
164    None,
165    PartialClone,
166    Default,
167}
168
169fn parse_partial_clone_attr(field: &Field) -> PartialCloneAttr {
170    field.attrs.iter().find_map(|attr| {
171        attr.path().is_ident("partial_clone").then(|| {
172            let mut partial = false;
173            let mut default = false;
174            attr.parse_nested_meta(|meta| {
175                if meta.path.is_ident("partial") {
176                    partial = true;
177                    Ok(())
178                } else if meta.path.is_ident("default") {
179                    default = true;
180                    Ok(())
181                } else {
182                    Err(meta.error("nested partial_clone attribute must be 'partial' or 'default'"))
183                }
184            }).expect("partial_clone attribute missing nested attribute of 'partial' or 'default'");
185
186            if partial && default {
187                panic!("partial_clone has both 'partial' and 'default' attributes, expected exactly one");
188            } else if partial {
189                PartialCloneAttr::PartialClone
190            } else if default {
191                PartialCloneAttr::Default
192            } else {
193                panic!("partial_clone attribute must have nested attribute of either 'partial' or 'default'");
194            }
195        })
196    }).unwrap_or(PartialCloneAttr::None)
197}