Skip to main content

qubit_argument/argument/
argument_value.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//! Lossless representations of scalar argument values.
9
10use std::fmt::{
11    self,
12    Debug,
13    Display,
14    Formatter,
15};
16use std::time::Duration;
17
18/// A scalar value captured for a validation constraint or error.
19///
20/// Floating-point values are stored as raw IEEE 754 bits, so equality and
21/// hashing preserve distinctions such as signed zero and NaN payloads.
22/// Duration values retain their exact seconds and nanoseconds. Formatting
23/// reconstructs floats and preserves unit-bearing duration diagnostics.
24///
25/// This enum is non-exhaustive. Downstream matches must include a wildcard arm
26/// so future scalar representations can be added without a breaking release.
27///
28/// ```compile_fail
29/// use qubit_argument::ArgumentValue;
30///
31/// fn classify(value: ArgumentValue) -> &'static str {
32///     match value {
33///         ArgumentValue::Signed(_) => "signed",
34///         ArgumentValue::Unsigned(_) => "unsigned",
35///         ArgumentValue::Float32(_) => "f32",
36///         ArgumentValue::Float64(_) => "f64",
37///         ArgumentValue::Duration(_) => "duration",
38///     }
39/// }
40/// ```
41///
42/// ```compile_fail
43/// #![deny(unused_must_use)]
44/// use qubit_argument::ArgumentValue;
45///
46/// ArgumentValue::Signed(1);
47/// ```
48#[non_exhaustive]
49#[must_use]
50#[derive(Clone, Copy, PartialEq, Eq, Hash)]
51pub enum ArgumentValue {
52    /// A signed integer represented without loss.
53    Signed(i128),
54    /// An unsigned integer represented without loss.
55    Unsigned(u128),
56    /// The raw bits of a 32-bit floating-point value.
57    Float32(u32),
58    /// The raw bits of a 64-bit floating-point value.
59    Float64(u64),
60    /// An exact standard-library duration value.
61    Duration(Duration),
62}
63
64macro_rules! impl_from_signed_integer {
65    ($($source:ty),+ $(,)?) => {
66        $(
67            impl From<$source> for ArgumentValue {
68                /// Converts a signed primitive integer without losing its value.
69                #[inline(always)]
70                fn from(value: $source) -> Self {
71                    Self::Signed(value as i128)
72                }
73            }
74        )+
75    };
76}
77
78macro_rules! impl_from_unsigned_integer {
79    ($($source:ty),+ $(,)?) => {
80        $(
81            impl From<$source> for ArgumentValue {
82                /// Converts an unsigned primitive integer without losing its value.
83                #[inline(always)]
84                fn from(value: $source) -> Self {
85                    Self::Unsigned(value as u128)
86                }
87            }
88        )+
89    };
90}
91
92impl_from_signed_integer!(i8, i16, i32, i64, i128, isize);
93impl_from_unsigned_integer!(u8, u16, u32, u64, u128, usize);
94
95impl From<f32> for ArgumentValue {
96    /// Captures the exact IEEE 754 bit pattern of a 32-bit float.
97    #[inline(always)]
98    fn from(value: f32) -> Self {
99        Self::Float32(value.to_bits())
100    }
101}
102
103impl From<f64> for ArgumentValue {
104    /// Captures the exact IEEE 754 bit pattern of a 64-bit float.
105    #[inline(always)]
106    fn from(value: f64) -> Self {
107        Self::Float64(value.to_bits())
108    }
109}
110
111impl From<Duration> for ArgumentValue {
112    /// Captures an exact standard-library duration value.
113    #[inline(always)]
114    fn from(value: Duration) -> Self {
115        Self::Duration(value)
116    }
117}
118
119impl Debug for ArgumentValue {
120    /// Formats the variant with its reconstructed scalar value.
121    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
122        match self {
123            Self::Signed(value) => {
124                formatter.debug_tuple("Signed").field(value).finish()
125            }
126            Self::Unsigned(value) => {
127                formatter.debug_tuple("Unsigned").field(value).finish()
128            }
129            Self::Float32(bits) => formatter
130                .debug_tuple("Float32")
131                .field(&f32::from_bits(*bits))
132                .finish(),
133            Self::Float64(bits) => formatter
134                .debug_tuple("Float64")
135                .field(&f64::from_bits(*bits))
136                .finish(),
137            Self::Duration(value) => {
138                formatter.debug_tuple("Duration").field(value).finish()
139            }
140        }
141    }
142}
143
144impl Display for ArgumentValue {
145    /// Formats the represented scalar value without a variant label.
146    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
147        match self {
148            Self::Signed(value) => Display::fmt(value, formatter),
149            Self::Unsigned(value) => Display::fmt(value, formatter),
150            Self::Float32(bits) => {
151                Display::fmt(&f32::from_bits(*bits), formatter)
152            }
153            Self::Float64(bits) => {
154                Display::fmt(&f64::from_bits(*bits), formatter)
155            }
156            Self::Duration(value) => Debug::fmt(value, formatter),
157        }
158    }
159}