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;
12
13#[cfg(feature = "json")]
14use qubit_budget::MeasuredBudgetError;
15#[cfg(feature = "json")]
16use qubit_budget::ResourceQuantity;
17#[cfg(feature = "json")]
18use qubit_budget::json::JsonValueBudget;
19
20use super::Value;
21use super::ValueRepr;
22use crate::identity::canonical_f32_bits;
23use crate::identity::canonical_f64_bits;
24#[cfg(feature = "big-decimal")]
25use crate::identity::hash_big_decimal;
26#[cfg(feature = "json")]
27use crate::identity::hash_json;
28use crate::identity::hash_string_map;
29#[cfg(feature = "json")]
30use crate::identity::json_eq;
31
32/// Compares one pair of same-variant storage payloads by semantic identity.
33macro_rules! payload_eq {
34    (Float32, $left:expr, $right:expr) => {
35        canonical_f32_bits(*$left) == canonical_f32_bits(*$right)
36    };
37    (Float64, $left:expr, $right:expr) => {
38        canonical_f64_bits(*$left) == canonical_f64_bits(*$right)
39    };
40    (Json, $left:expr, $right:expr) => {
41        json_eq($left, $right)
42    };
43    ($variant:ident, $left:expr, $right:expr) => {
44        $left == $right
45    };
46}
47
48/// Hashes one storage payload using the semantic identity contract.
49macro_rules! hash_payload {
50    (Float32, $value:expr, $state:expr) => {
51        canonical_f32_bits(*$value).hash($state)
52    };
53    (Float64, $value:expr, $state:expr) => {
54        canonical_f64_bits(*$value).hash($state)
55    };
56    (BigDecimal, $value:expr, $state:expr) => {
57        hash_big_decimal($value, $state)
58    };
59    (StringMap, $value:expr, $state:expr) => {
60        hash_string_map($value, $state)
61    };
62    (Json, $value:expr, $state:expr) => {
63        hash_json($value, $state)
64    };
65    ($variant:ident, $value:expr, $state:expr) => {
66        $value.hash($state)
67    };
68}
69
70/// Keeps JSON transaction handling in the caller while dispatching other types.
71#[cfg(feature = "json")]
72macro_rules! budgeted_hash_payload {
73    (Json, $value:expr, $state:expr) => {{
74        let _ = $value;
75        unreachable!("JSON payload hashing is handled by Value::hash_with_json_budget")
76    }};
77    ($variant:ident, $value:expr, $state:expr) => {
78        hash_payload!($variant, $value, $state)
79    };
80}
81
82/// Generates the non-JSON payload dispatch from the storage type table.
83#[cfg(feature = "json")]
84macro_rules! budgeted_payload_match {
85    ($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)*)),+ $(,)?) => {
86        match $repr {
87            ValueRepr::Unset(data_type) => data_type.hash($state),
88            $($(#[$cfg])* ValueRepr::$variant(value) => {
89                budgeted_hash_payload!($variant, value, $state)
90            },)+
91        }
92    };
93}
94
95/// Hashes one value payload while applying a budget to JSON payloads.
96///
97/// # Type Parameters
98///
99/// * `H` - Hasher receiving the semantic payload identity.
100/// * `R` - Resource identifier used by the JSON budget.
101/// * `Q` - Quantity type used by the JSON budget.
102///
103/// # Parameters
104///
105/// * `repr` - Private scalar representation whose payload is hashed.
106/// * `state` - Destination hasher.
107/// * `_budget` - Budget reserved for JSON payload accounting by the caller.
108///
109/// # Returns
110///
111/// `Ok(())` after the payload identity is hashed.
112///
113/// # Errors
114///
115/// This helper currently returns no error for non-JSON payloads; JSON payloads
116/// are preflighted by
117/// [`Value::hash_with_json_budget`](crate::Value::hash_with_json_budget).
118#[cfg(feature = "json")]
119pub(crate) fn hash_value_payload_with_json_budget<H, R, Q>(
120    repr: &ValueRepr,
121    state: &mut H,
122    _budget: &mut JsonValueBudget<R, Q>,
123) -> Result<(), MeasuredBudgetError<R, Q>>
124where
125    H: Hasher,
126    R: Clone,
127    Q: ResourceQuantity,
128{
129    for_each_value_type!(budgeted_payload_match, repr, state);
130    Ok(())
131}
132
133/// Implements equality and hashing for the private value representation.
134macro_rules! impl_value_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 Value {
140            fn eq(&self, other: &Self) -> bool {
141                match (&self.repr, &other.repr) {
142                    (ValueRepr::Unset(left), ValueRepr::Unset(right)) => left == right,
143                    $($(#[$cfg])*
144                    (ValueRepr::$variant(left), ValueRepr::$variant(right)) => {
145                        payload_eq!($variant, left, right)
146                    },)+
147                    _ => false,
148                }
149            }
150        }
151
152        impl Eq for Value {}
153
154        impl Hash for Value {
155            fn hash<H: Hasher>(&self, state: &mut H) {
156                std::mem::discriminant(&self.repr).hash(state);
157                match &self.repr {
158                    ValueRepr::Unset(data_type) => data_type.hash(state),
159                    $($(#[$cfg])*
160                    ValueRepr::$variant(value) => hash_payload!($variant, value, state),)+
161                }
162            }
163        }
164    };
165}
166
167for_each_value_type!(impl_value_identity);