derive_constructors_proc/
lib.rs

1use proc_macro::TokenStream;
2use std::collections::HashMap;
3use std::str::FromStr;
4use convert_case::Casing;
5use proc_macro2::Ident;
6use quote::{quote, ToTokens};
7use syn::{Data, DataEnum, DeriveInput, parse_macro_input};
8use syn::parse::Parse;
9use parsing_structs::{FieldsInfo, TryFromInfo};
10use crate::utils::{ExpectElseOption, ExpectElseResult, print_info};
11
12mod utils;
13
14/// Allows you to define a constructor function, inside the proc attribute you can customize the
15/// implementation by giving this information following attributes (Note every attribute is
16/// optional):
17///
18/// - named: Name of the function, constructor functions are usually named like
19/// 'with_*name of the fields*', as calling them are quite readable, like
20/// ```CharacterInfo::with_name_and_age("Jorge", 23)```. <br> Note: If this field isn't given,
21/// instead of implementing a 'with_*' constructor function, it will implement the [From] or
22/// [TryFrom] trait.
23///
24/// - pattern (values: [From, TryFrom], default: From):
25///     - When using the From pattern, the function receives fields as parameters and returns this
26/// struct with said values, this is what you'll be looking for most of the time.
27///     - When using the TryFrom pattern, the functions receives types that implement
28/// Into<YourField1>, Into<YourField2>..., returning a [Ok] with your struct if every field could
29/// successfully be turned to your field, in case not, it will return [Err] with an enum telling
30/// which field couldn't get initialized and the Error why it didn't, see examples below for this.
31///
32/// - fields (default: All fields not included in the '```defaults```' attribute): Name of the
33/// fields you want to create your constructor for, for example: ```fields(age, name)``` could
34/// result in a function like: ```fn new(age: u8, name: String) -> CharacterInfo```.
35///
36/// - defaults: Tells how to initialize fields not covered in the ```fields``` attribute, for
37/// example ```defaults(years_studied(4))```. <br>If a field isn't either on the ```fields``` or
38/// ```defaults``` attributes, it would count as it was initialized through [Default::default], this
39/// means, the ```times_appeared``` field that hasn't been covered will be init as 0 (since
40/// u8::default() is 0).
41///
42/// - error_enum_named (Only for the TryFrom pattern): Specifies the name for the enum error that
43/// it's returned the TryFrom function fails.
44///
45/// - error_enum_metadata (Only for the TryFrom pattern): Declares the metadata for the enum error
46/// that it's returned the TryFrom function fails, you will most likely want to write
47/// ```error_enum_metadata(#[derive(Debug)])``` in there.
48/// <br><br>
49///
50/// ## 2.1 Example 1: Empty constructor
51///
52/// If you just apply the [constructor] attribute, it will just implement the [From] trait where it
53/// will take a tuple formed out of all your fields, in this case,
54/// ```from(value: (String, u8)) -> CharacterInfo```.
55///
56/// ``` rust
57/// #[derive(Debug, PartialEq)]
58/// #[derive_constructors_proc::constructor]
59/// struct CharacterInfo{
60///     name: String,
61///     age: u8,
62/// }
63///
64/// let character_using_from = CharacterInfo::from(("Jorge".to_string(), 23));
65/// let expected_character = CharacterInfo { name: "Jorge".to_string(), age: 23 };
66/// assert_eq!(character_using_from, expected_character);
67/// ```
68/// <br>
69///
70/// ## 2.2 Example 2: A 'new' constructor using specific fields
71///
72/// The following example creates a function named ```new(name: String, age: u8) -> CharacterInfo```
73/// .<br><br>
74/// Since ```years_studied``` is specified, it will be initialized as 4, and since
75/// ```times_appeared``` is not, it will be initialized as u8::default() (which is 0).
76///
77/// ``` rust
78/// #[derive(Debug, PartialEq)]
79/// #[derive_constructors_proc::constructor(named(new), fields(name, age), defaults(years_studied(4)))]
80/// struct CharacterInfo{
81///     name: String,
82///     age: u8,
83///     times_appeared: u8,
84///     years_studied: u8
85/// }
86///
87/// let character_using_from = CharacterInfo::new("Jorge".to_string(), 23);
88/// let expected_character = CharacterInfo { name: "Jorge".to_string(), age: 23, times_appeared: 0, years_studied: 4};
89/// assert_eq!(character_using_from, expected_character);
90/// ```
91/// <br>
92///
93/// ## 2.3 Example 3: A 'new' constructor with the TryFrom pattern
94///
95/// The following example creates a function named ```new(name: T where String: TryFrom<T>, age: U
96/// where String: TryFrom<U>) -> Result<CharacterInfo, MyEnumError>```.<br><br>
97/// Since ```years_studied``` is specified, it will be initialized as 4, and since
98/// ```times_appeared``` is not, it will be initialized as u8::default() (which is 0).<br><br>
99/// In case of an error, it returns a variant of an enum named ```MyEnumError```, this enum is
100/// specified to derive [Debug] and [PartialEq].
101///
102/// ``` rust
103/// let character_using_try_from = CharacterInfo::new("Jorge", 23_u16).unwrap();
104/// let expected_character = CharacterInfo { name: "Jorge".to_string(), age: 23, times_appeared: 0, years_studied: 4};
105/// assert_eq!(character_using_try_from, expected_character);
106///
107/// let produced_error = u8::try_from(23000_u16).unwrap_err();
108/// let forced_error_using_try_from = CharacterInfo::new("Jorge", 23000_u16).unwrap_err();
109/// let expected_error_on_try_from = MyEnumError::AgeError(produced_error);
110/// assert_eq!(forced_error_using_try_from, expected_error_on_try_from);
111///
112/// #[derive(Debug, PartialEq)]
113/// #[derive_constructors_proc::constructor(
114///     named(new),
115///     fields(name, age),
116///     defaults(years_studied(4)),
117///     pattern(TryFrom),
118///     error_enum_named(MyEnumError),
119///     error_enum_metadata(#[derive(Debug, PartialEq)])
120/// )]
121/// struct CharacterInfo{
122///     name: String,
123///     age: u8,
124///     times_appeared: u8,
125///     years_studied: u8,
126/// }
127/// ```
128#[proc_macro_attribute]
129pub fn constructor(attr: TokenStream, mut item: TokenStream) -> TokenStream {
130    let item_cloned = item.clone();
131    let derive_input = parse_macro_input!(item_cloned as DeriveInput);
132    let data = match derive_input.data.clone() {
133        Data::Struct(data) => data,
134        _ => panic!("This attribute macro is only implemented for structs"),
135    };
136
137    let mut attr_contents = utils::idents_and_groups_from(attr.clone())
138        .expect_else(|_| "Could not resolve groups and descriptions")
139        .into_iter()
140        .map(|(ident, group)| (ident.to_string(), group))
141        .collect::<HashMap<_, _>>();
142
143    let constructor_fn_name = attr_contents.remove("named")
144        .map(|constructor_name| syn::parse::<Ident>(constructor_name.into())
145            .expect_else(|_| "Could not get name for constructor's function"));
146
147    let constructor_pattern = attr_contents.remove("pattern")
148        .map(|pattern| pattern.to_string().to_lowercase())
149        .unwrap_or_else(|| "from".to_string());
150    let constructor_pattern = match constructor_pattern.as_str() {
151        "from" => Pattern::From,
152        "tryfrom" => Pattern::TryFrom,
153        wrong_pattern => panic!("This constructor is asking for a pattern by the name of '{wrong_pattern}', the only patterns available are 'From' and 'TryFrom' ")
154    };
155
156    let fields_info = FieldsInfo::new_from_macro_attribute_info(&data, &mut attr_contents);
157
158    let ex = match constructor_pattern {
159        Pattern::From => {
160            tokens_for__from__for_struct(derive_input.ident, fields_info, constructor_fn_name)
161        }
162        Pattern::TryFrom => {
163            let try_from_info = TryFromInfo::new_from_macro_attribute_info(&derive_input, &fields_info, constructor_fn_name.as_ref(), &mut attr_contents);
164            tokens_for__try_from__for_struct(derive_input.ident, fields_info, try_from_info, constructor_fn_name)
165        }
166    };
167
168
169    item.extend(Into::<TokenStream>::into(ex));
170    item
171}
172
173enum Pattern {
174    From,
175    TryFrom,
176}
177
178/// On structs it allows to Derive the [From] trait where a tuple of the fields are passed to the
179/// [From::from], for example:
180///
181/// ``` rust
182/// #[derive(derive_constructors_proc::From, PartialEq, Debug)]
183/// struct CharacterInfo{
184///     name: String,
185///     age: u8,
186///     #[no_from]
187///     times_appeared: u8,
188///     #[no_from(4)]
189///     years_studied: u8
190/// }
191///
192/// let character_using_from = CharacterInfo::from(("Jorge".to_string(), 23));
193/// let expected_character = CharacterInfo { name: "Jorge".to_string(), age: 23, times_appeared: 0, years_studied: 4};
194/// assert_eq!(character_using_from, expected_character);
195/// ```
196/// <br><br>
197/// 
198/// On enums it implement the [From] trait by creating a From::from function for each variant taking
199/// it's values as parameters, for example:
200///
201/// ```rust
202/// #[derive(derive_constructors_proc::From, Debug, PartialEq)]
203/// enum MyValue{
204///     StaticString(&'static str),
205///     Number(i32),
206///     Boolean(bool),
207/// }
208///
209/// let scattered_values = vec![MyValue::from("Age "), MyValue::from(23), MyValue::from(", over age "), MyValue::from(true)];
210/// let specified = vec![MyValue::StaticString("Age "), MyValue::Number(23), MyValue::StaticString(", over age "), MyValue::Boolean(true)];
211/// assert_eq!(scattered_values, specified);
212/// ```
213#[proc_macro_derive(From, attributes(no_from))]
214pub fn derive_from(input: TokenStream) -> TokenStream {
215    /*    let cloned_input = input.clone();
216    let p = format!("{:#?}\n", parse_macro_input!(cloned_input as DeriveInput));
217    print_info(|| "Derive input info", || p);*/
218    let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);
219    match data {
220        Data::Union(_) => panic!("The 'From' derive_constructors_proc macro targets structs and enums, consider removing '#[derive_constructors_proc(From)]' for this type"),
221        Data::Struct(data_struct) => return tokens_for__from__for_struct(ident, FieldsInfo::new_from_derive_data_struct(&data_struct), None),
222        Data::Enum(data_enum) => return tokens_for__from__for_enum(ident, data_enum),
223    };
224}
225
226/// It derives [TryFrom] trait where a tuple of this struct's fields are passed to the
227/// [TryFrom::try_from] function, for example
228///
229/// ``` rust
230/// #[derive(derive_constructors_proc::TryFrom, PartialEq, Debug)]
231/// #[enum_error_meta(#[derive(Debug, PartialEq)])]
232/// struct CharacterInfo{
233///     name: String,
234///     age: u8,
235///     #[no_from]
236///     times_appeared: u8,
237///     #[no_from(4)]
238///     years_studied: u8
239/// }
240///
241/// let character_using_try_from = CharacterInfo::try_from(("Jorge", 23_u16)).unwrap();
242/// let expected_character = CharacterInfo { name: "Jorge".to_string(), age: 23, times_appeared: 0, years_studied: 4};
243/// assert_eq!(character_using_try_from, expected_character);
244///
245/// let produced_error = u8::try_from(23000_u16).unwrap_err();
246/// let forced_error_using_try_from = CharacterInfo::try_from(("Jorge", 23000_u16)).unwrap_err();
247/// let expected_error_on_try_from = CharacterInfoTryFromError::AgeError(produced_error);
248/// assert_eq!(forced_error_using_try_from, expected_error_on_try_from);
249/// ```
250#[proc_macro_derive(TryFrom, attributes(no_from, enum_error_meta))]
251pub fn derive_try_from(input: TokenStream) -> TokenStream {
252    let cloned_input = input.clone();
253    let p = format!("{:#?}\n", parse_macro_input!(cloned_input as DeriveInput));
254    print_info(|| "Derive input info", || p);
255
256    let DeriveInput { ident, data, attrs, .. } = parse_macro_input!(input as DeriveInput);
257    match data {
258        Data::Union(_) | Data::Enum(_) => panic!("The 'From' derive_constructors_proc macro targets structs, consider removing '#[derive_constructors_proc(From)]' for this type"),
259        Data::Struct(data_struct) => {
260            let fields_info = FieldsInfo::new_from_derive_data_struct(&data_struct);
261            let try_from_info = TryFromInfo::new_from_derive_data_struct(&ident, &attrs, &fields_info.fields_names);
262            return tokens_for__try_from__for_struct(ident, fields_info, try_from_info, None);
263        }
264    };
265}
266
267
268fn tokens_for__try_from__for_struct(name: Ident, fields_info: FieldsInfo, try_from_info: TryFromInfo, constructor_fn_name: Option<Ident>) -> TokenStream {
269    let FieldsInfo {
270        fields_names,
271        fields_types,
272        no_from_fields,
273        no_from_fields_initializers
274    }
275        = fields_info;
276
277    let TryFromInfo {
278        error_enum_metadata,
279        error_enum_name,
280        error_types,
281        try_from_types
282    }
283        = try_from_info;
284
285
286    if constructor_fn_name.is_none() {
287        let res = quote! {
288            #error_enum_metadata
289            pub enum #error_enum_name <#(#error_types),*>{
290                #(#error_types (#error_types)),*
291            }
292
293            impl <#(#try_from_types , #error_types),*>
294                TryFrom<(#(#try_from_types),*)> for #name
295                where
296                    #(#fields_types : TryFrom< #try_from_types, Error=#error_types > ),*
297            {
298
299                type Error = #error_enum_name<#(#error_types),*>;
300
301                fn try_from(value: (#(#try_from_types),*)) -> Result<Self, Self::Error> {
302
303                    let (#(#fields_names),*) = value;
304                    #(let #fields_names = <#fields_types>::try_from(#fields_names)
305                        .map_err(|error| #error_enum_name::#error_types(error)  )?; )*
306                    Ok(
307                        #name{
308                            #(#fields_names,)*
309                            #(#no_from_fields: #no_from_fields_initializers,)*
310                        }
311                    )
312                }
313            }
314        };
315        print_info(|| "Derive input res", || format!("{res}"));
316        return res.into();
317    }
318
319    let constructor_fn_name = constructor_fn_name.unwrap();
320    let res = quote! {
321        #error_enum_metadata
322        pub enum #error_enum_name <#(#error_types),*>{
323            #(#error_types (#error_types)),*
324        }
325
326        impl #name {
327            pub fn #constructor_fn_name<#(#try_from_types , #error_types),*>(#(#fields_names: #try_from_types),*) -> Result<Self, #error_enum_name<#(#error_types),*>>
328                where
329                    #(#fields_types : TryFrom< #try_from_types, Error=#error_types > ),*
330            {
331                    #(let #fields_names = <#fields_types>::try_from(#fields_names)
332                        .map_err(|error| #error_enum_name::#error_types(error)  )?; )*
333                    Ok(
334                        #name{
335                            #(#fields_names,)*
336                            #(#no_from_fields: #no_from_fields_initializers,)*
337                        }
338                    )
339            }
340        }
341
342    };
343
344    print_info(|| "Output", || format!("{res}"));
345    res.into()
346}
347
348fn tokens_for__from__for_struct(name: Ident, fields_info: FieldsInfo, constructor_fn_name: Option<Ident>) -> TokenStream {
349    let FieldsInfo {
350        fields_names, fields_types,
351        no_from_fields, no_from_fields_initializers
352    } = fields_info;
353
354    if constructor_fn_name.is_none() {
355        let res = quote! {
356            impl core::convert::From<(#(#fields_types),*)> for #name {
357                fn from(value: (#(#fields_types),* )) -> Self {
358                    let (#(#fields_names),*) = value;
359                    Self {
360                        #(#fields_names,)*
361                        #(#no_from_fields : #no_from_fields_initializers),*
362                    }
363                }
364            }
365        };
366        print_info(|| "Derive input res", || format!("{res}"));
367        return res.into();
368    }
369
370    let constructor_fn_name = constructor_fn_name.unwrap();
371    let res = quote! {
372            impl #name{
373                pub fn #constructor_fn_name( #(#fields_names: #fields_types),*  ) -> Self{
374                    Self {
375                        #(#fields_names,)*
376                        #(#no_from_fields : #no_from_fields_initializers),*
377                    }
378                }
379            }
380        };
381    print_info(|| "Derive input res", || format!("{res}"));
382    res.into()
383}
384
385fn tokens_for__from__for_enum(name: Ident, enum_data: DataEnum) -> TokenStream {
386    let impls = enum_data.variants.iter()
387        .filter(|variant| utils::find_attribute(&variant.attrs, "no_from").is_none())
388        .map(|variant| {
389            let variant_name = &variant.ident;
390            let (fieldnames, types) = variant.fields.iter()
391                .map(|fields| (fields.ident.as_ref().map(ToString::to_string), fields.ty.to_token_stream()))
392                .unzip::<_, _, Vec<_>, Vec<_>>();
393            let is_named = fieldnames.get(0).is_some_and(|name| name.is_some());
394
395            let fieldnames = fieldnames
396                .into_iter()
397                .enumerate()
398                .map(|(index, name)| name.unwrap_or_else(|| index.to_string()))
399                .map(|name| name.parse::<proc_macro2::TokenStream>().unwrap())
400                .collect::<Vec<_>>();
401            print_info(|| format!("Variant {variant:?}"),
402                       || format!("Is named: {is_named}\n\
403                        fields names :{fieldnames:#?}\
404                        \n fields types :{types:#?}\n"));
405            let indexes =
406                match fieldnames.len() {
407                    0 => Vec::new(),
408                    1 => vec!["".parse::<proc_macro2::TokenStream>().unwrap()],
409                    _ => {
410                        (0..fieldnames.len())
411                            .map(|index| format!(".{index}").parse::<proc_macro2::TokenStream>().unwrap())
412                            .collect::<Vec<_>>()
413                    }
414                };
415
416            let res = quote! {
417                impl core::convert::From<(#(#types),*)> for #name {
418                    fn from(value: (#(#types),* )) -> Self {
419                        Self:: #variant_name { #(#fieldnames : value #indexes),* }
420                    }
421                }
422            };
423            print_info(|| "Possible result", || format!("{res}"));
424            res
425        })
426        .collect::<Vec<_>>();
427    let res = impls.into_iter().reduce(|token1, token2| {
428        quote! { #token1 #token2}
429    }).unwrap();
430    print_info(|| "Output", || format!("{res}"));
431    TokenStream::from(res)
432}
433
434pub(crate) mod parsing_structs;