1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
#![allow(clippy::needless_doctest_main)]
//! # Automation of Destructure Pattern
//! `destructure` is a automation library for `destructure pattern`.
//! 
//! ## Usage
//! ```rust
//! use destructure::Destructure;
//! 
//! #[derive(Destructure)]
//! pub struct Book {
//!     id: u64,
//!     name: String,
//!     stocked_at: String,
//!     author: String,
//!     // ... too many fields...
//! }
//! 
//! fn main() {
//!     let book = Book {
//!         id: 1234_5678_9999_0000u64,
//!         name: "name".to_string(),
//!         stocked_at: "2023/01/03".to_string(),
//!         author: "author".to_string()
//!     };
//! 
//!     // Auto generate
//!     let des: DestructBook = book.into_destruct();
//! 
//!     println!("{:?}", des.id);
//! }
//! ```
//! 
//! ### What is `destructure pattern`?
//! A structure with too many fields makes it hard to call constructors,
//! but it is also hard work to prepare a `Getter/Setter` for each one.
//! There are macros for this purpose, but even so, a large number of macros reduces readability.
//! This is especially true when using `From<T>` Trait.
//! 
//! So how can this be simplified? It is the technique of "converting all fields to public". 
//!   
//! This allows for a simplified representation, as in the following example
//! 
//! ```rust
//! pub struct Book {
//!     id: u64,
//!     name: String,
//!     stocked_at: String,
//!     author: String,
//!     // ... too many fields...
//! }
//! 
//! impl Book {
//!     pub fn into_destruct(self) -> DestructBook {
//!         DestructBook {
//!             id: self.id,
//!             name: self.name,
//!             stocked_at: self.stocked_at,
//!             author: self.author,
//!         }
//!     }
//! }
//! 
//! pub struct DestructBook {
//!     pub id: u64,
//!     pub name: String,
//!     pub stocked_at: String,
//!     pub author: String,
//!     // ... too many fields (All `public`.)...
//! }
//!
//! fn main() {
//!     let book = Book {
//!         id: 1234_5678_9999_0000u64,
//!         name: "name".to_string(),
//!         stocked_at: "2023/01/03".to_string(),
//!         author: "author".to_string()
//!     };
//!     
//!     let des = book.into_destruct();
//! 
//!     println!("{:?}", des.id);
//! }
//! ```
//!   
//! There are several problems with this method, the most serious of which is the increase in boilerplate.  
//! Using the multi-cursor feature of the editor, this can be done by copy-pasting, but it is still a hassle.  
//! 
//! Therefore, I created a *Procedural Macro* that automatically generates structures and methods:
//! 
//! ```rust
//! use destructure::Destructure;
//!
//! #[derive(Destructure)]
//! pub struct Book {
//!     id: u64,
//!     name: String,
//!     stocked_at: String,
//!     author: String,
//!     // ... too many fields...
//! }
//!
//! fn main() {
//!     let book = Book {
//!         id: 1234_5678_9999_0000u64,
//!         name: "name".to_string(),
//!         stocked_at: "2023/01/03".to_string(),
//!         author: "author".to_string()
//!     };
//!
//!     // Auto generate
//!     let des: DestructBook = book.into_destruct();
//!
//!     println!("{:?}", des.id);
//! }
//!```
//!
//! You can also perform safe value substitution by using `reconstruct()`,
//! which performs the same role as the following usage.
//! ```rust
//! use destructure::Destructure;
//!
//! #[derive(Debug, Eq, PartialEq, Clone, Destructure)]
//! pub struct Book {
//!     id: u64,
//!     name: String,
//!     stocked_at: String,
//!     author: String,
//!     // ... too many fields...
//! }
//!
//! fn main() {
//!    let before = Book {
//!        id: 1234_5678_9999_0000u64,
//!        name: "name".to_string(),
//!        stocked_at: "2023/01/03".to_string(),
//!        author: "author".to_string()
//!    };
//!
//!    let author = "After".to_string();
//!
//!    // Auto generate
//!    let after = before.clone().reconstruct(|before| {
//!        before.author = author;
//!    });
//!
//!    assert_ne!(before, after);
//! }
//! ```

use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use syn::{
    parse_macro_input,
    DeriveInput,
    Ident,
    Data,
    DataStruct,
    Fields,
    FieldsNamed,
};

/// Automatically implements `into_destruct()` and `freeze()` methods.
//noinspection DuplicatedCode
#[proc_macro_derive(Destructure)]
pub fn derive_destructure(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let generate = format!("Destruct{}", name);
    let generate_ident = Ident::new(&generate, name.span());

    let fields = if let Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { ref named, ..}), .. }) = ast.data {
        named
    } else {
        return quote_spanned! { name.span() => compile_error!("Only structures with named fields are supported.") }.into()
    };

    let destruction = fields.iter().map(|field| {
        let name = &field.ident;
        let ty = &field.ty;
        quote! {
            pub #name: #ty
        }
    });

    let expanded = fields.iter().map(|field| {
        let name = &field.ident;
        quote! {
            #name: self.#name
        }
    });

    let freeze = expanded.clone();

    let q = quote::quote! {
        /// Do not have an explicit implementation for this structure.
        pub struct #generate_ident {
            #(#destruction,)*
        }

        impl #name {
            /// Convert the field value to a fully disclosed Destruct structure.
            /// 
            /// If you wish to revert the Destruct structure back to the original structure, see `freeze()`.
            pub fn into_destruct(self) -> #generate_ident {
                #generate_ident { #(#expanded,)* }
            }

            /// It provides a mechanism for replacing the contents by [`into_destruct()`]
            /// and changing the actual value by [`freeze()`] using a limited closure.
            ///
            /// If you wish to use Result, see [`try_reconstruct()`].
            pub fn reconstruct(self, f: impl FnOnce(&mut #generate_ident)) -> #name {
                let mut dest = self.into_destruct();
                f(&mut dest);
                dest.freeze()
            }

            pub fn try_reconstruct<E>(self, f: impl FnOnce(&mut #generate_ident) -> Result<(), E>) -> Result<#name, E> {
                let mut dest = self.into_destruct();
                f(&mut dest)?;
                Ok(dest.freeze())
            }
        }

        impl #generate_ident {
            /// Restore the Destruct structure to its original structure again.
            pub fn freeze(self) -> #name {
                #name { #(#freeze,)* }
            }
        }
    };

    q.into()
}


/// Automatically implements `substitute()` methods.
///
/// When performing loop processing, and so on,
/// it is more efficient than using [`reconstruct()`].
/// ## Usage
/// ```rust
/// use destructure::Mutation;
///
/// #[derive(Debug, Mutation)]
/// pub struct Book {
///     id: String,
///     name: String,
/// }
///
/// # fn main() {
/// # let mut book = Book { id: "123456789-abc".to_string(), name: "name".to_string() };
/// book.substitute(|book| {
///     *book.name = "new name".to_string();
/// });
///
/// book.try_substitute(|book| -> Result<(), std::io::Error> {
///    *book.name = "new name".to_string();
///    Ok(())
/// }).expect("Error");
/// # }
//noinspection DuplicatedCode
#[proc_macro_derive(Mutation)]
pub fn derive_mutation(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let name = &ast.ident;

    let generate = format!("{}Mut", name);
    let generate_ident = Ident::new(&generate, name.span());

    let fields = if let Data::Struct(DataStruct { fields: Fields::Named(FieldsNamed { ref named, ..}), .. }) = ast.data {
        named
    } else {
        return quote_spanned! { name.span() => compile_error!("Only structures with named fields are supported.") }.into()
    };

    let destruction = fields.iter().map(|field| {
        let name = &field.ident;
        let ty = &field.ty;
        quote! {
            pub #name: &'mutation mut #ty
        }
    });

    let expanded = fields.iter().map(|field| {
        let name = &field.ident;
        quote! {
            #name: &mut self.#name
        }
    });

    let expanded_cloned = expanded.clone();

    let q = quote::quote! {
        /// Do not have an explicit implementation for this structure.
        pub struct #generate_ident<'mutation> {
            #(#destruction,)*
        }

        impl #name {
            pub fn substitute(&mut self, mut f: impl FnOnce(&mut #generate_ident)) {
                f(&mut #generate_ident {
                    #(#expanded,)*
                })
            }

            pub fn try_substitute<E>(&mut self, mut f: impl FnOnce(&mut #generate_ident) -> Result<(), E>) -> Result<(), E> {
                f(&mut #generate_ident {
                    #(#expanded_cloned,)*
                })
            }
        }
    };

    q.into()
}