patchable_macros/
lib.rs

1//! Derive macro(s) for patchable structs in Rust.
2//! You probably want [`patchable`](https://docs.rs/patchable) instead.
3
4use proc_macro::TokenStream;
5use syn::*;
6use quote::*;
7
8/// Creates a patch struct and implements [`Patchable`](trait@patchable_core::Patchable).
9/// 
10/// ```
11/// #[derive(Patchable)]
12/// struct MyStruct {
13///     foo: String,
14///     bar: i32
15/// }
16/// ```
17/// would generate 
18/// ```
19/// pub struct MyStructPatch {
20///     pub foo: Option<String>,
21///     pub bar: Option<String>
22/// }
23/// 
24/// impl Patchable<MyStructPatch> for MyStruct {
25///     // skipped for brevity
26/// }
27/// ```
28/// 
29/// You can also specify using the `#[patch(PatchType)]` attribute to change both the name of the generated struct,
30/// and the type of the replacement field in the generate struct. This works as long as the field type implements `Patchable<PatchType>`.
31/// 
32/// This allows for nesting of patches.
33/// 
34/// ```
35/// #[derive(Patchable)]
36/// #[patch(MyPatch)]
37/// struct MyStruct {
38///     foo: String,
39///     #[patch(BarPatch)]
40///     bar: Bar,
41/// }
42/// 
43/// #[derive(Patchable)]
44/// struct Bar {
45///     foobar: i32,
46/// }
47/// ```
48/// would generate
49/// ```
50/// struct MyPatch {
51///     foo: Option<String>,
52///     bar: BarPatch,
53/// }
54/// 
55/// struct BarPatch {
56///     foobar: Option<i32>,
57/// }
58/// // Patchable impls...
59/// ```
60#[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}