Skip to main content

qubit_value/multi_values/
multi_values_identity.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Equality and hashing for [`super::MultiValues`].
9
10use std::hash::Hash;
11use std::hash::Hasher;
12use std::mem::discriminant;
13
14use super::MultiValuesRepr;
15use super::multi_values::MultiValues;
16use crate::identity::canonical_f32_bits;
17use crate::identity::canonical_f64_bits;
18#[cfg(feature = "big-decimal")]
19use crate::identity::hash_big_decimal;
20#[cfg(feature = "json")]
21use crate::identity::hash_json;
22use crate::identity::hash_string_map;
23#[cfg(feature = "json")]
24use crate::identity::json_eq;
25
26/// Compares ordered payloads using the identity rule for their element type.
27macro_rules! payloads_eq {
28    (Float32, $left:expr, $right:expr) => {
29        $left.len() == $right.len()
30            && $left
31                .iter()
32                .zip($right)
33                .all(|(left, right)| canonical_f32_bits(*left) == canonical_f32_bits(*right))
34    };
35    (Float64, $left:expr, $right:expr) => {
36        $left.len() == $right.len()
37            && $left
38                .iter()
39                .zip($right)
40                .all(|(left, right)| canonical_f64_bits(*left) == canonical_f64_bits(*right))
41    };
42    (Json, $left:expr, $right:expr) => {
43        $left.len() == $right.len() && $left.iter().zip($right).all(|(left, right)| json_eq(left, right))
44    };
45    ($variant:ident, $left:expr, $right:expr) => {
46        $left == $right
47    };
48}
49
50/// Hashes ordered payloads using the identity rule for their element type.
51macro_rules! hash_payloads {
52    (Float32, $values:expr, $state:expr) => {{
53        $values.len().hash($state);
54        for value in $values {
55            canonical_f32_bits(*value).hash($state);
56        }
57    }};
58    (Float64, $values:expr, $state:expr) => {{
59        $values.len().hash($state);
60        for value in $values {
61            canonical_f64_bits(*value).hash($state);
62        }
63    }};
64    (BigDecimal, $values:expr, $state:expr) => {{
65        $values.len().hash($state);
66        for value in $values {
67            hash_big_decimal(value, $state);
68        }
69    }};
70    (StringMap, $values:expr, $state:expr) => {{
71        $values.len().hash($state);
72        for value in $values {
73            hash_string_map(value, $state);
74        }
75    }};
76    (Json, $values:expr, $state:expr) => {{
77        $values.len().hash($state);
78        for value in $values {
79            hash_json(value, $state);
80        }
81    }};
82    ($variant:ident, $values:expr, $state:expr) => {
83        $values.hash($state)
84    };
85}
86
87/// Keeps JSON transaction handling in the caller while dispatching other types.
88#[cfg(feature = "json")]
89macro_rules! non_json_hash_payload {
90    (Json, $value:expr, $state:expr) => {{
91        let _ = $value;
92        unreachable!("JSON payload hashing is handled by MultiValues::hash_with_json_budget")
93    }};
94    ($variant:ident, $value:expr, $state:expr) => {
95        hash_payloads!($variant, $value, $state)
96    };
97}
98
99/// Generates the non-JSON payload dispatch from the storage type table.
100#[cfg(feature = "json")]
101macro_rules! non_json_payload_match {
102    ($repr:expr, $state:expr; $(([$($cfg:meta),*], $variant:ident, $type:ty, $data_type:expr, $materialization:ident, $json_class:ident, $number_projection:ident, $value_doc:literal, $multi_doc:literal $(, $_wire:tt)*)),+ $(,)?) => {
103        match $repr {
104            MultiValuesRepr::Unset(data_type) => data_type.hash($state),
105            $($(#[$cfg])* MultiValuesRepr::$variant(value) => {
106                non_json_hash_payload!($variant, value, $state)
107            },)+
108        }
109    };
110}
111
112/// Hashes a non-JSON collection payload. JSON is handled by
113/// [`MultiValues::hash_with_json_budget`](crate::MultiValues::hash_with_json_budget).
114///
115/// # Type Parameters
116///
117/// * `H` - Hasher receiving the collection payload's semantic identity.
118///
119/// # Parameters
120///
121/// * `repr` - Non-JSON collection storage to hash, excluding its variant tag.
122/// * `state` - Hasher updated with the payload's semantic identity.
123///
124/// # Panics
125///
126/// Panics if `repr` contains JSON storage; the caller must handle that variant
127/// through the budget-aware hashing path instead.
128#[cfg(feature = "json")]
129pub(crate) fn hash_non_json_multi_values_payload<H: Hasher>(repr: &MultiValuesRepr, state: &mut H) {
130    for_each_value_type!(non_json_payload_match, repr, state);
131}
132
133/// Implements lawful equality and hashing for the complete value-type table.
134macro_rules! impl_multi_values_identity {
135    (
136        ;
137        $(([$($cfg:meta),*], $variant:ident, $type:ty, $data_type:expr, $materialization:ident, $json_class:ident, $number_projection:ident, $value_doc:literal, $multi_doc:literal $(, $_wire:tt)*)),+ $(,)?
138    ) => {
139        impl PartialEq for MultiValues {
140            /// Compares variants and ordered payloads using semantic identity.
141            ///
142            /// # Parameters
143            ///
144            /// * `other` - Collection to compare with this borrowed collection.
145            ///
146            /// # Returns
147            ///
148            /// Whether both collections have the same variant and equal payloads.
149            fn eq(&self, other: &Self) -> bool {
150                match (&self.repr, &other.repr) {
151                    (MultiValuesRepr::Unset(left), MultiValuesRepr::Unset(right)) => left == right,
152                    $($(#[$cfg])*
153                    (MultiValuesRepr::$variant(left), MultiValuesRepr::$variant(right)) => {
154                        payloads_eq!($variant, left, right)
155                    },)+
156                    _ => false,
157                }
158            }
159        }
160
161        impl Eq for MultiValues {}
162
163        impl Hash for MultiValues {
164            /// Writes the variant tag and ordered semantic payload identity.
165            ///
166            /// # Type Parameters
167            ///
168            /// * `H` - Hasher receiving the collection identity.
169            ///
170            /// # Parameters
171            ///
172            /// * `state` - Hasher updated with the variant and payload identity.
173            fn hash<H: Hasher>(&self, state: &mut H) {
174                discriminant(&self.repr).hash(state);
175                match &self.repr {
176                    MultiValuesRepr::Unset(data_type) => data_type.hash(state),
177                    $($(#[$cfg])*
178                    MultiValuesRepr::$variant(values) => {
179                        hash_payloads!($variant, values, state)
180                    },)+
181                }
182            }
183        }
184    };
185}
186
187for_each_value_type!(impl_multi_values_identity);