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}