From f8e3e4ae8e3aadfc31267de49077948e763c8e1b Mon Sep 17 00:00:00 2001 From: Intege-rs Date: Tue, 3 Feb 2026 03:33:48 -0500 Subject: [PATCH] ai generated --- .gitignore | 2 + Cargo.lock | 169 +++++++++++++++++++++++++++++++ Cargo.toml | 12 +++ examples/basic_usage.rs | 98 ++++++++++++++++++ hash2_derive/Cargo.toml | 12 +++ hash2_derive/src/lib.rs | 126 +++++++++++++++++++++++ src/lib.rs | 218 ++++++++++++++++++++++++++++++++++++++++ tests/derive_test.rs | 164 ++++++++++++++++++++++++++++++ 8 files changed, 801 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 examples/basic_usage.rs create mode 100644 hash2_derive/Cargo.toml create mode 100644 hash2_derive/src/lib.rs create mode 100644 src/lib.rs create mode 100644 tests/derive_test.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a0038a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1048aa9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,169 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "hash2" +version = "0.1.0" +dependencies = [ + "hash2_derive", + "ordered-float", + "uuid", +] + +[[package]] +name = "hash2_derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7a904b9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "hash2" +version = "0.1.0" +edition = "2024" + +[features] + +[dependencies] +ordered-float = "5.1.0" +hash2_derive = { path = "hash2_derive" } + +uuid = { version = "*", optional = true } diff --git a/examples/basic_usage.rs b/examples/basic_usage.rs new file mode 100644 index 0000000..7a5ef07 --- /dev/null +++ b/examples/basic_usage.rs @@ -0,0 +1,98 @@ +use hash2::Hash2; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; + +// Basic struct with derive +#[derive(Hash2)] +pub struct User { + pub id: u64, + pub name: String, + pub age: u8, +} + +// Struct with generics +#[derive(Hash2)] +pub struct Container { + pub value: T, + pub count: usize, +} + +// Struct with lifetimes +#[derive(Hash2)] +pub struct Borrowed<'a> { + pub data: &'a str, + pub id: u64, +} + +// Struct with both generics and lifetimes +#[derive(Hash2)] +pub struct Complex<'a, T> { + pub owned: T, + pub borrowed: &'a str, +} + +// Example with PhantomData (like the original Test struct) +#[derive(Hash2)] +pub struct Test<'a> { + pub test: f32, + pub test2: u64, + pub _ignored: std::marker::PhantomData<&'a ()>, +} + +// Enum example +#[derive(Hash2)] +pub enum Status { + Active, + Inactive, + Pending { since: u64 }, +} + +fn compute_hash(value: &T) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() +} + +fn main() { + // Basic struct + let user = User { + id: 42, + name: "Alice".to_string(), + age: 30, + }; + println!("User hash: {}", compute_hash(&user)); + + // Generic struct + let container = Container { + value: 100u32, + count: 5, + }; + println!("Container hash: {}", compute_hash(&container)); + + // Borrowed struct + let text = String::from("Hello"); + let borrowed = Borrowed { + data: &text, + id: 1, + }; + println!("Borrowed hash: {}", compute_hash(&borrowed)); + + // Complex struct + let complex = Complex { + owned: vec![1, 2, 3], + borrowed: &text, + }; + println!("Complex hash: {}", compute_hash(&complex)); + + // Test struct (original example) + let test = Test { + test: 3.14, + test2: 999, + _ignored: std::marker::PhantomData, + }; + println!("Test hash: {}", compute_hash(&test)); + + // Enum + let status = Status::Pending { since: 12345 }; + println!("Status hash: {}", compute_hash(&status)); +} diff --git a/hash2_derive/Cargo.toml b/hash2_derive/Cargo.toml new file mode 100644 index 0000000..19f539a --- /dev/null +++ b/hash2_derive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "hash2_derive" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" diff --git a/hash2_derive/src/lib.rs b/hash2_derive/src/lib.rs new file mode 100644 index 0000000..e1a6d8d --- /dev/null +++ b/hash2_derive/src/lib.rs @@ -0,0 +1,126 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput, Fields, GenericParam, Generics}; + +#[proc_macro_derive(Hash2)] +pub fn derive_hash2(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = &input.ident; + let generics = &input.generics; + + // Extract field hashing logic + let hash_fields = match &input.data { + Data::Struct(data) => { + match &data.fields { + Fields::Named(fields) => { + let field_names = fields.named.iter().map(|f| &f.ident); + quote! { + #( + ::hash2::Hash2::hash(&self.#field_names, state); + )* + } + } + Fields::Unnamed(fields) => { + let field_indices = (0..fields.unnamed.len()).map(syn::Index::from); + quote! { + #( + ::hash2::Hash2::hash(&self.#field_indices, state); + )* + } + } + Fields::Unit => { + quote! {} + } + } + } + Data::Enum(data) => { + let variants = data.variants.iter().map(|variant| { + let variant_name = &variant.ident; + match &variant.fields { + Fields::Named(fields) => { + let field_names: Vec<_> = fields.named.iter().map(|f| &f.ident).collect(); + let field_names2 = field_names.clone(); + quote! { + #name::#variant_name { #(#field_names),* } => { + #( + ::hash2::Hash2::hash(#field_names2, state); + )* + } + } + } + Fields::Unnamed(fields) => { + let field_names: Vec<_> = (0..fields.unnamed.len()) + .map(|i| syn::Ident::new(&format!("f{}", i), proc_macro2::Span::call_site())) + .collect(); + let field_names2 = field_names.clone(); + quote! { + #name::#variant_name(#(#field_names),*) => { + #( + ::hash2::Hash2::hash(#field_names2, state); + )* + } + } + } + Fields::Unit => { + quote! { + #name::#variant_name => {} + } + } + } + }); + + quote! { + match self { + #(#variants)* + } + } + } + Data::Union(_) => { + panic!("Hash2 cannot be derived for unions"); + } + }; + + // Build where clause with Hash2 bounds for generic types + let where_clause = build_where_clause(generics); + + // Split generics for impl + let (impl_generics, ty_generics, _) = generics.split_for_impl(); + + let expanded = quote! { + impl #impl_generics ::hash2::Hash2 for #name #ty_generics #where_clause { + fn hash(&self, state: &mut H) { + #hash_fields + } + } + }; + + TokenStream::from(expanded) +} + +fn build_where_clause(generics: &Generics) -> proc_macro2::TokenStream { + let mut predicates = Vec::new(); + + // Add Hash2 bound for each type parameter (but not lifetimes) + for param in &generics.params { + if let GenericParam::Type(type_param) = param { + let ident = &type_param.ident; + predicates.push(quote! { #ident: ::hash2::Hash2 }); + } + } + + // Include existing where predicates + if let Some(where_clause) = &generics.where_clause { + for predicate in &where_clause.predicates { + predicates.push(quote! { #predicate }); + } + } + + if predicates.is_empty() { + quote! {} + } else { + quote! { + where #(#predicates),* + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d0dbec0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,218 @@ +pub use core::hash::Hasher; +pub use hash2_derive::Hash2; + +use std::hash::Hash; +use ordered_float::OrderedFloat; + +pub trait Hash2 { + fn hash(&self, state: &mut H); +} + +#[cfg(feature = "uuid")] +impl Hash2 for uuid::Uuid { + fn hash(&self, state: &mut H) { + let (a,b) = self.as_u64_pair(); + state.write_u64(a); + state.write_u64(b); + } +} + +// Unsigned integer implementations +impl Hash2 for u8 { + fn hash(&self, state: &mut H) { + state.write_u8(*self); + } +} + +impl Hash2 for u16 { + fn hash(&self, state: &mut H) { + state.write_u16(*self); + } +} + +impl Hash2 for u32 { + fn hash(&self, state: &mut H) { + state.write_u32(*self); + } +} + +impl Hash2 for u64 { + fn hash(&self, state: &mut H) { + state.write_u64(*self); + } +} + +impl Hash2 for u128 { + fn hash(&self, state: &mut H) { + state.write_u128(*self); + } +} + +impl Hash2 for usize { + fn hash(&self, state: &mut H) { + state.write_usize(*self); + } +} + +// Signed integer implementations +impl Hash2 for i8 { + fn hash(&self, state: &mut H) { + state.write_i8(*self); + } +} + +impl Hash2 for i16 { + fn hash(&self, state: &mut H) { + state.write_i16(*self); + } +} + +impl Hash2 for i32 { + fn hash(&self, state: &mut H) { + state.write_i32(*self); + } +} + +impl Hash2 for i64 { + fn hash(&self, state: &mut H) { + state.write_i64(*self); + } +} + +impl Hash2 for i128 { + fn hash(&self, state: &mut H) { + state.write_i128(*self); + } +} + +impl Hash2 for isize { + fn hash(&self, state: &mut H) { + state.write_isize(*self); + } +} + +// floats +impl Hash2 for f32 { + fn hash(&self, state: &mut H) { + OrderedFloat(*self).hash(state) + } +} + +impl Hash2 for f64 { + fn hash(&self, state: &mut H) { + OrderedFloat(*self).hash(state) + } +} + +// String types +impl Hash2 for str { + fn hash(&self, state: &mut H) { + state.write(self.as_bytes()); + state.write_usize(self.len()); + } +} + +impl Hash2 for String { + fn hash(&self, state: &mut H) { + Hash2::hash(self.as_str(), state); + } +} + +impl Hash2 for &T { + fn hash(&self, state: &mut H) { + (*self).hash(state); + } +} + +impl Hash2 for &mut T { + fn hash(&self, state: &mut H) { + (**self).hash(state); + } +} + +impl Hash2 for [T] { + fn hash(&self, state: &mut H) { + state.write_usize(self.len()); + for item in self { + item.hash(state); + } + } +} + +impl Hash2 for [T; N] { + fn hash(&self, state: &mut H) { + for item in self { + item.hash(state); + } + } +} + +impl Hash2 for Vec { + fn hash(&self, state: &mut H) { + self.as_slice().hash(state); + } +} + +impl Hash2 for Option { + fn hash(&self, state: &mut H) { + match self { + None => state.write_u8(0), + Some(value) => { + state.write_u8(1); + value.hash(state); + } + } + } +} + + +impl Hash2 for bool { + fn hash(&self, state: &mut H) { + state.write_u8(*self as u8); + } +} + +impl Hash2 for char { + fn hash(&self, state: &mut H) { + state.write_u32(*self as u32); + } +} + +// Tuple implementations +impl Hash2 for std::marker::PhantomData { + fn hash(&self, _state: &mut H) {} +} + +impl Hash2 for () { + fn hash(&self, _state: &mut H) {} +} + +impl Hash2 for (T,) { + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl Hash2 for (T, U) { + fn hash(&self, state: &mut H) { + self.0.hash(state); + self.1.hash(state); + } +} + +impl Hash2 for (T, U, V) { + fn hash(&self, state: &mut H) { + self.0.hash(state); + self.1.hash(state); + self.2.hash(state); + } +} + +impl Hash2 for (T, U, V, W) { + fn hash(&self, state: &mut H) { + self.0.hash(state); + self.1.hash(state); + self.2.hash(state); + self.3.hash(state); + } +} diff --git a/tests/derive_test.rs b/tests/derive_test.rs new file mode 100644 index 0000000..2675d4c --- /dev/null +++ b/tests/derive_test.rs @@ -0,0 +1,164 @@ +use hash2::Hash2; + +#[derive(Hash2)] +struct SimpleStruct { + a: u32, + b: u64, +} + +#[derive(Hash2)] +struct GenericStruct { + value: T, + count: usize, +} + +#[derive(Hash2)] +struct LifetimeStruct<'a> { + name: &'a str, + id: u64, +} + +#[derive(Hash2)] +struct BothGenerics<'a, T> { + data: T, + reference: &'a str, + number: i32, +} + +#[derive(Hash2)] +struct MultipleTypes { + first: T, + second: U, +} + +#[derive(Hash2)] +struct MultipleLifetimes<'a, 'b> { + first: &'a str, + second: &'b str, +} + +#[derive(Hash2)] +struct Complex<'a, 'b, T, U> +where + T: Clone, + U: Clone, +{ + data1: T, + data2: U, + ref1: &'a str, + ref2: &'b str, +} + +#[derive(Hash2)] +enum SimpleEnum { + A, + B(u32), + C { x: i32, y: i32 }, +} + +#[derive(Hash2)] +enum GenericEnum { + None, + Some(T), + Pair(T, T), +} + +fn hash_value(value: &T) -> u64 { + use std::collections::hash_map::DefaultHasher; + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + std::hash::Hasher::finish(&hasher) +} + +#[test] +fn test_simple_struct() { + let s = SimpleStruct { a: 42, b: 1000 }; + let hash1 = hash_value(&s); + let hash2 = hash_value(&s); + assert_eq!(hash1, hash2, "Same struct should produce same hash"); +} + +#[test] +fn test_generic_struct() { + let s1 = GenericStruct { value: 42u32, count: 10 }; + let s2 = GenericStruct { value: 42u32, count: 10 }; + assert_eq!(hash_value(&s1), hash_value(&s2)); + + let s3 = GenericStruct { value: "hello", count: 10 }; + let _ = hash_value(&s3); +} + +#[test] +fn test_lifetime_struct() { + let name = String::from("test"); + let s = LifetimeStruct { name: &name, id: 123 }; + let hash1 = hash_value(&s); + let hash2 = hash_value(&s); + assert_eq!(hash1, hash2); +} + +#[test] +fn test_both_generics() { + let reference = String::from("ref"); + let s = BothGenerics { + data: 42u32, + reference: &reference, + number: -10, + }; + let _ = hash_value(&s); +} + +#[test] +fn test_multiple_types() { + let s = MultipleTypes { + first: 42u32, + second: "hello", + }; + let _ = hash_value(&s); +} + +#[test] +fn test_multiple_lifetimes() { + let first = String::from("first"); + let second = String::from("second"); + let s = MultipleLifetimes { + first: &first, + second: &second, + }; + let _ = hash_value(&s); +} + +#[test] +fn test_complex() { + let ref1 = String::from("ref1"); + let ref2 = String::from("ref2"); + let s = Complex { + data1: 42u32, + data2: "data", + ref1: &ref1, + ref2: &ref2, + }; + let _ = hash_value(&s); +} + +#[test] +fn test_simple_enum() { + let e1 = SimpleEnum::A; + let e2 = SimpleEnum::B(42); + let e3 = SimpleEnum::C { x: 1, y: 2 }; + + let _ = hash_value(&e1); + let _ = hash_value(&e2); + let _ = hash_value(&e3); +} + +#[test] +fn test_generic_enum() { + let e1: GenericEnum = GenericEnum::None; + let e2 = GenericEnum::Some(42u32); + let e3 = GenericEnum::Pair(1u32, 2u32); + + let _ = hash_value(&e1); + let _ = hash_value(&e2); + let _ = hash_value(&e3); +}