1use proc_macro::TokenStream;
5use syn::*;
6use quote::*;
7
8#[proc_macro_derive(Patchable, attributes(patch))]
61pub fn derive(input: TokenStream) -> TokenStream {
62 let input = parse_macro_input!(input as DeriveInput);
63 let input_name = input.ident;
64
65 let patch_name = if let Some(name) = parse_attrs_ident(input.attrs) {
66 name
67 } else {
68 format_ident!("{}Patch", input_name)
69 };
70
71 let mut names = Vec::new();
72 let mut types = Vec::new();
73 let mut patch_types = Vec::new();
74
75 match input.data {
76 Data::Struct(struct_data) => {
77 match struct_data.fields {
78 Fields::Named(named_fields) => {
79 for field in named_fields.named {
80 let ty = field.ty;
81 names.push(field.ident.unwrap());
82 types.push(quote!{#ty});
83 if let Some(ident) = parse_attrs_ident(field.attrs) {
84 patch_types.push(quote!{#ident});
85 } else {
86 patch_types.push(quote!{::core::option::Option<#ty>});
87 }
88 }
89 },
90 Fields::Unnamed(_unnamed_fields) => unimplemented!(),
91 Fields::Unit => unimplemented!(),
92 }
93 },
94 Data::Enum(_enum_data) => unimplemented!(),
95 Data::Union(_union_data) => unimplemented!(),
96 }
97
98 let patch_struct = quote!{
99 pub struct #patch_name {
100 #(pub #names: #patch_types),*
101 }
102 };
103
104 let patchable_impl = quote!{
105 impl ::patchable_core::Patchable<#patch_name> for #input_name {
106 fn apply_patch(&mut self, patch: #patch_name) {
107 #(
108 self.#names.apply_patch(patch.#names);
109 )*
110 }
111 }
112 };
113
114 let output = quote!{
115 #patch_struct
116 #patchable_impl
117 };
118
119 TokenStream::from(output)
120}
121
122fn parse_attrs_ident(attrs: Vec<Attribute>) -> Option<Ident> {
123 for attr in attrs {
124 if let Some(ident) = parse_attr_ident(attr) {
125 return Some(ident);
126 }
127 }
128 None
129}
130
131fn parse_attr_ident(attr: Attribute) -> Option<Ident> {
132 if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
133 if meta_list.path.is_ident("patch") {
134 if let Some(NestedMeta::Meta(Meta::Path(path))) = meta_list.nested.first() {
135 return path.get_ident().cloned();
136 }
137 }
138 }
139 None
140}