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()
44            && $left
45                .iter()
46                .zip($right)
47                .all(|(left, right)| json_eq(left, right))
48    };
49    ($variant:ident, $left:expr, $right:expr) => {
50        $left == $right
51    };
52}
53
54/// Hashes ordered payloads using the identity rule for their element type.
55macro_rules! hash_payloads {
56    (Float32, $values:expr, $state:expr) => {{
57        $values.len().hash($state);
58        for value in $values {
59            canonical_f32_bits(*value).hash($state);
60        }
61    }};
62    (Float64, $values:expr, $state:expr) => {{
63        $values.len().hash($state);
64        for value in $values {
65            canonical_f64_bits(*value).hash($state);
66        }
67    }};
68    (BigDecimal, $values:expr, $state:expr) => {{
69        $values.len().hash($state);
70        for value in $values {
71            hash_big_decimal(value, $state);
72        }
73    }};
74    (StringMap, $values:expr, $state:expr) => {{
75        $values.len().hash($state);
76        for value in $values {
77            hash_string_map(value, $state);
78        }
79    }};
80    (Json, $values:expr, $state:expr) => {{
81        $values.len().hash($state);
82        for value in $values {
83            hash_json(value, $state);
84        }
85    }};
86    ($variant:ident, $values:expr, $state:expr) => {
87        $values.hash($state)
88    };
89}
90
91/// Keeps JSON transaction handling in the caller while dispatching other types.
92#[cfg(feature = "json")]
93macro_rules! non_json_hash_payload {
94    (Json, $value:expr, $state:expr) => {{
95        let _ = $value;
96        unreachable!("JSON payload hashing is handled by MultiValues::hash_with_json_budget")
97    }};
98    ($variant:ident, $value:expr, $state:expr) => {
99        hash_payloads!($variant, $value, $state)
100    };
101}
102
103/// Generates the non-JSON payload dispatch from the storage type table.
104#[cfg(feature = "json")]
105macro_rules! non_json_payload_match {
106    ($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)*)),+ $(,)?) => {
107        match $repr {
108            MultiValuesRepr::Unset(data_type) => data_type.hash($state),
109            $($(#[$cfg])* MultiValuesRepr::$variant(value) => {
110                non_json_hash_payload!($variant, value, $state)
111            },)+
112        }
113    };
114}
115
116/// Hashes a non-JSON collection payload. JSON is handled by
117/// [`MultiValues::hash_with_json_budget`](crate::MultiValues::hash_with_json_budget).
118///
119/// # Type Parameters
120///
121/// * `H` - Hasher receiving the collection payload's semantic identity.
122///
123/// # Parameters
124///
125/// * `repr` - Non-JSON collection storage to hash, excluding its variant tag.
126/// * `state` - Hasher updated with the payload's semantic identity.
127///
128/// # Panics
129///
130/// Panics if `repr` contains JSON storage; the caller must handle that variant
131/// through the budget-aware hashing path instead.
132#[cfg(feature = "json")]
133pub(crate) fn hash_non_json_multi_values_payload<H: Hasher>(repr: &MultiValuesRepr, state: &mut H) {
134    for_each_value_type!(non_json_payload_match, repr, state);
135}
136
137/// Implements lawful equality and hashing for the complete value-type table.
138macro_rules! impl_multi_values_identity {
139    (
140        ;
141        $(([$($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)*)),+ $(,)?
142    ) => {
143        impl PartialEq for MultiValues {
144            /// Compares variants and ordered payloads using semantic identity.
145            ///
146            /// # Parameters
147            ///
148            /// * `other` - Collection to compare with this borrowed collection.
149            ///
150            /// # Returns
151            ///
152            /// Whether both collections have the same variant and equal payloads.
153            fn eq(&self, other: &Self) -> bool {
154                match (&self.repr, &other.repr) {
155                    (MultiValuesRepr::Unset(left), MultiValuesRepr::Unset(right)) => left == right,
156                    $($(#[$cfg])*
157                    (MultiValuesRepr::$variant(left), MultiValuesRepr::$variant(right)) => {
158                        payloads_eq!($variant, left, right)
159                    },)+
160                    _ => false,
161                }
162            }
163        }
164
165        impl Eq for MultiValues {}
166
167        impl Hash for MultiValues {
168            /// Writes the variant tag and ordered semantic payload identity.
169            ///
170            /// # Type Parameters
171            ///
172            /// * `H` - Hasher receiving the collection identity.
173            ///
174            /// # Parameters
175            ///
176            /// * `state` - Hasher updated with the variant and payload identity.
177            fn hash<H: Hasher>(&self, state: &mut H) {
178                discriminant(&self.repr).hash(state);
179                match &self.repr {
180                    MultiValuesRepr::Unset(data_type) => data_type.hash(state),
181                    $($(#[$cfg])*
182                    MultiValuesRepr::$variant(values) => {
183                        hash_payloads!($variant, values, state)
184                    },)+
185                }
186            }
187        }
188    };
189}
190
191for_each_value_type!(impl_multi_values_identity);