field_comparable_derive 0.1.1

Procedural macro to generate struct field comparison methods / Macro procédurale pour générer des méthodes de comparaison de champs de structures
Documentation
#![doc = include_str!("../README.md")]

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};

/// Macro procédurale qui génère des méthodes de comparaison pour les structures
/// Procedural macro that generates comparison methods for structs
///
/// Cette macro génère automatiquement des méthodes pour comparer les champs d'une structure,
/// permettant de détecter facilement les différences entre instances.
/// This macro automatically generates methods to compare struct fields,
/// making it easy to detect differences between instances.
///
/// # Exemple / Example
/// ```rust
/// use field_comparable_derive::Comparable;
///
/// #[derive(Comparable)]
/// struct User {
///     name: String,
///     age: i32,
///     email: Option<String>,
/// }
///
/// let user1 = User { name: "Alice".to_string(), age: 25, email: Some("alice@example.com".to_string()) };
/// let user2 = User { name: "Bob".to_string(), age: 25, email: Some("alice@example.com".to_string()) };
///
/// // Vérifie si les structures diffèrent / Check if structs differ
/// assert!(user1.differs_from(&user2));
///
/// // Vérifie un champ spécifique / Check a specific field
/// assert!(user1.name_differs_from("Bob".to_string()));
/// assert!(user1.age_differs_from(user2.age));
///
/// // Vérifie à partir d'une ref / Check from a ref
/// assert!(user1.name_differs_from_ref(&user2.name))
/// ```
#[proc_macro_derive(Comparable)]
pub fn comparable_derive(input: TokenStream) -> TokenStream {
    // Parse l'entrée de la macro / Parse the macro input
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    // Génère les méthodes selon le type de données / Generate methods based on data type
    let methods = match &input.data {
        Data::Struct(data_struct) => {
            match &data_struct.fields {
                Fields::Named(fields) => {
                    // Génère les méthodes individuelles pour chaque champ
                    // Generate individual methods for each field
                    let field_methods = fields.named.iter().map(|field| {
                        let field_name = field.ident.as_ref().expect("Field should have a name");
                        let field_type = &field.ty;

                        // Créé les noms des méthodes / Create method names
                        let method_name = syn::Ident::new(
                            &format!("{}_differs_from", field_name),
                            field_name.span()
                        );
                        let method_name_ref = syn::Ident::new(
                            &format!("{}_differs_from_ref", field_name),
                            field_name.span()
                        );

                        // Documentation pour les méthodes générées
                        // Documentation for generated methods
                        let field_doc = format!("Vérifie si le champ `{}` diffère de la valeur donnée / Check if field `{}` differs from the given value", field_name, field_name);
                        let field_ref_doc = format!("Vérifie si le champ `{}` diffère de la référence donnée / Check if field `{}` differs from the given reference", field_name, field_name);

                        quote! {
                            #[doc = #field_doc]
                            pub fn #method_name(&self, other: #field_type) -> bool {
                                Self::compare_values(&self.#field_name, &other)
                            }

                            #[doc = #field_ref_doc]
                            pub fn #method_name_ref(&self, other: &#field_type) -> bool {
                                Self::compare_values(&self.#field_name, other)
                            }
                        }
                    });

                    // Génère les vérifications pour la méthode differs_from
                    // Generate checks for the differs_from method
                    let differs_from_checks = fields.named.iter().map(|field| {
                        let field_name = field.ident.as_ref().expect("Field should have a name");
                        quote! {
                            // Comparaison du champ / Field comparison
                            if Self::compare_values(&self.#field_name, &other.#field_name) {
                                return true;
                            }
                        }
                    });

                    quote! {
                        #(#field_methods)*

                        /// Vérifie si cette instance diffère d'une autre instance de la même structure
                        /// Check if this instance differs from another instance of the same struct
                        ///
                        /// # Arguments
                        /// * `other` - L'autre instance à comparer / The other instance to compare with
                        ///
                        /// # Returns
                        /// `true` si au moins un champ diffère, `false` sinon
                        /// `true` if at least one field differs, `false` otherwise
                        pub fn differs_from(&self, other: &Self) -> bool {
                            #(#differs_from_checks)*
                            false
                        }

                        /// Méthode utilitaire pour comparer deux valeurs
                        /// Utility method to compare two values
                        ///
                        /// Cette méthode utilise des références pour éviter les clones inutiles
                        /// This method uses references to avoid unnecessary clones
                        ///
                        /// # Type Parameters
                        /// * `T` - Type qui implémente `PartialEq` / Type that implements `PartialEq`
                        ///
                        /// # Arguments
                        /// * `a` - Première valeur à comparer / First value to compare
                        /// * `b` - Deuxième valeur à comparer / Second value to compare
                        ///
                        /// # Returns
                        /// `true` si les valeurs sont différentes, `false` si elles sont égales
                        /// `true` if values are different, `false` if they are equal
                        fn compare_values<T: PartialEq>(a: &T, b: &T) -> bool {
                            a != b
                        }
                    }
                }
                Fields::Unnamed(_) => {
                    quote! {
                        compile_error!("Comparable ne peut être dérivé que pour les structures avec des champs nommés / Comparable can only be derived for structs with named fields");
                    }
                }
                Fields::Unit => {
                    quote! {
                        compile_error!("Comparable ne peut être dérivé que pour les structures avec des champs / Comparable can only be derived for structs with fields");
                    }
                }
            }
        }
        Data::Enum(_) => {
            quote! {
                compile_error!("Comparable ne peut être dérivé que pour les structures, pas les énumérations / Comparable can only be derived for structs, not enums");
            }
        }
        Data::Union(_) => {
            quote! {
                compile_error!("Comparable ne peut être dérivé que pour les structures, pas les unions / Comparable can only be derived for structs, not unions");
            }
        }
    };

    // Génère l'implémentation finale / Generate the final implementation
    let expanded = quote! {
        impl #name {
            #methods
        }
    };

    TokenStream::from(expanded)
}