Skip to main content

qubit_value/value/
value_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::Value`].
9
10use std::hash::Hash;
11use std::hash::Hasher;
12use std::mem::discriminant;
13
14use super::Value;
15use super::ValueRepr;
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 one pair of same-variant storage payloads by semantic identity.
27macro_rules! payload_eq {
28    (Float32, $left:expr, $right:expr) => {
29        canonical_f32_bits(*$left) == canonical_f32_bits(*$right)
30    };
31    (Float64, $left:expr, $right:expr) => {
32        canonical_f64_bits(*$left) == canonical_f64_bits(*$right)
33    };
34    (Json, $left:expr, $right:expr) => {
35        json_eq($left, $right)
36    };
37    ($variant:ident, $left:expr, $right:expr) => {
38        $left == $right
39    };
40}
41
42/// Hashes one storage payload using the semantic identity contract.
43macro_rules! hash_payload {
44    (Float32, $value:expr, $state:expr) => {
45        canonical_f32_bits(*$value).hash($state)
46    };
47    (Float64, $value:expr, $state:expr) => {
48        canonical_f64_bits(*$value).hash($state)
49    };
50    (BigDecimal, $value:expr, $state:expr) => {
51        hash_big_decimal($value, $state)
52    };
53    (StringMap, $value:expr, $state:expr) => {
54        hash_string_map($value, $state)
55    };
56    (Json, $value:expr, $state:expr) => {
57        hash_json($value, $state)
58    };
59    ($variant:ident, $value:expr, $state:expr) => {
60        $value.hash($state)
61    };
62}
63
64/// Keeps JSON transaction handling in the caller while dispatching other types.
65#[cfg(feature = "json")]
66macro_rules! non_json_hash_payload {
67    (Json, $value:expr, $state:expr) => {{
68        let _ = $value;
69        unreachable!("JSON payload hashing is handled by Value::hash_with_json_budget")
70    }};
71    ($variant:ident, $value:expr, $state:expr) => {
72        hash_payload!($variant, $value, $state)
73    };
74}
75
76/// Generates the non-JSON payload dispatch from the storage type table.
77#[cfg(feature = "json")]
78macro_rules! non_json_payload_match {
79    ($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)*)),+ $(,)?) => {
80        match $repr {
81            ValueRepr::Unset(data_type) => data_type.hash($state),
82            $($(#[$cfg])* ValueRepr::$variant(value) => {
83                non_json_hash_payload!($variant, value, $state)
84            },)+
85        }
86    };
87}
88
89/// Hashes a non-JSON value payload. JSON is handled by
90/// [`Value::hash_with_json_budget`](crate::Value::hash_with_json_budget).
91///
92/// # Type Parameters
93///
94/// - `H`: Hasher receiving the payload's semantic identity.
95///
96/// # Parameters
97///
98/// - `repr`: Borrowed non-JSON storage payload; its variant discriminant is
99///   hashed separately by the caller.
100/// - `state`: Hasher updated with the payload, or the declared type for an
101///   unset value. The payload is neither cloned nor consumed.
102///
103/// # Panics
104///
105/// Panics if `repr` contains a JSON payload; callers must handle JSON through
106/// the budget-aware path before calling this helper.
107#[cfg(feature = "json")]
108pub(crate) fn hash_non_json_value_payload<H: Hasher>(repr: &ValueRepr, state: &mut H) {
109    for_each_value_type!(non_json_payload_match, repr, state);
110}
111
112/// Implements equality and hashing for the private value representation.
113macro_rules! impl_value_identity {
114    (
115        ;
116        $(([$($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)*)),+ $(,)?
117    ) => {
118        impl PartialEq for Value {
119            /// Compares variant identity and semantic payload identity.
120            ///
121            /// # Parameters
122            ///
123            /// - `other`: Value borrowed for comparison without consuming it.
124            ///
125            /// # Returns
126            ///
127            /// Whether both values have the same variant and equal payloads,
128            /// with floating-point NaNs and signed zero canonicalized.
129            fn eq(&self, other: &Self) -> bool {
130                match (&self.repr, &other.repr) {
131                    (ValueRepr::Unset(left), ValueRepr::Unset(right)) => left == right,
132                    $($(#[$cfg])*
133                    (ValueRepr::$variant(left), ValueRepr::$variant(right)) => {
134                        payload_eq!($variant, left, right)
135                    },)+
136                    _ => false,
137                }
138            }
139        }
140
141        impl Eq for Value {}
142
143        impl Hash for Value {
144            /// Hashes the variant and payload consistently with equality.
145            ///
146            /// # Type Parameters
147            ///
148            /// - `H`: Hasher receiving the semantic identity.
149            ///
150            /// # Parameters
151            ///
152            /// - `state`: Hasher updated without cloning or consuming the value.
153            fn hash<H: Hasher>(&self, state: &mut H) {
154                discriminant(&self.repr).hash(state);
155                match &self.repr {
156                    ValueRepr::Unset(data_type) => data_type.hash(state),
157                    $($(#[$cfg])*
158                    ValueRepr::$variant(value) => hash_payload!($variant, value, state),)+
159                }
160            }
161        }
162    };
163}
164
165for_each_value_type!(impl_value_identity);