1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
//! `FiniteFloat` struct and basic methods module
use quote::quote;
use crate::config::TypeConfig;
use crate::doc_generator;
use crate::generator::{build_validation_expr, for_all_constraint_float_types, make_type_alias};
/// Generates concrete struct definitions for each constraint × float type combination
pub fn generate_concrete_structs(config: &TypeConfig) -> proc_macro2::TokenStream {
let structs =
for_all_constraint_float_types(config, |type_name, float_type, constraint_def| {
let struct_name = make_type_alias(type_name, float_type); // e.g., FinF32
let constraint_name = type_name; // e.g., Fin
let struct_doc =
doc_generator::generate_struct_doc(type_name, float_type, constraint_def);
quote! {
#[doc = #struct_doc]
#[repr(transparent)]
#[derive(Clone, Copy)]
#[expect(clippy::approx_constant)]
pub struct #struct_name {
value: #float_type,
_constraint: core::marker::PhantomData<#constraint_name>,
}
}
});
quote! {
// Concrete struct definitions
#(#structs)*
}
}
/// Generates implementations for each concrete struct
pub fn generate_concrete_impls(config: &TypeConfig) -> proc_macro2::TokenStream {
let impls = for_all_constraint_float_types(config, |type_name, float_type, constraint_def| {
let struct_name = make_type_alias(type_name, float_type);
let validate_expr = build_validation_expr(constraint_def, float_type);
let new_method_doc =
doc_generator::generate_new_method_doc(&struct_name, float_type, constraint_def);
quote! {
impl #struct_name {
#[doc = #new_method_doc]
#[must_use]
pub fn new(value: #float_type) -> Result<Self, FloatError> {
let val_f64: f64 = value.into();
// Check for NaN
if val_f64.is_nan() {
return Err(FloatError::NaN);
}
// Check for positive infinity
if val_f64.is_infinite() && val_f64 > 0.0 {
return Err(FloatError::PosInf);
}
// Check for negative infinity
if val_f64.is_infinite() && val_f64 < 0.0 {
return Err(FloatError::NegInf);
}
// Validate bounds and zero exclusion
if #validate_expr {
Ok(Self {
value,
_constraint: core::marker::PhantomData,
})
} else {
Err(FloatError::OutOfRange)
}
}
/// Unsafely creates a finite floating-point number (no validation)
///
/// # Safety
///
/// Caller must ensure the value satisfies the constraint.
/// Violating the constraint leads to undefined behavior.
#[inline]
pub const unsafe fn new_unchecked(value: #float_type) -> Self {
Self {
value,
_constraint: core::marker::PhantomData,
}
}
/// Gets the inner value
///
/// # Example
///
/// ```
/// use strict_num_extended::FinF32;
///
/// let finite = FinF32::new(2.5);
/// assert_eq!(finite.unwrap().get(), 2.5);
/// ```
#[must_use]
pub const fn get(&self) -> #float_type {
self.value
}
/// Gets the inner value (alias for `get()`)
///
/// # Example
///
/// ```
/// use strict_num_extended::FinF32;
///
/// let finite = FinF32::new(2.5);
/// assert_eq!(finite.unwrap().value(), 2.5);
/// ```
#[must_use]
pub const fn value(&self) -> #float_type {
self.value
}
/// Creates a value at compile time
///
/// # Panics
///
/// Will [`panic`] at compile time or runtime if the value does not satisfy the constraint.
#[inline]
#[must_use]
pub const fn new_const(value: #float_type) -> Self {
if #validate_expr {
unsafe { Self::new_unchecked(value) }
} else {
panic!("Value does not satisfy the constraint");
}
}
}
}
});
quote! {
#(#impls)*
}
}
/// Generates serde support for concrete types
pub fn generate_concrete_serde_impls(config: &TypeConfig) -> proc_macro2::TokenStream {
let serialize_impls = for_all_constraint_float_types(config, |type_name, float_type, _| {
let struct_name = make_type_alias(type_name, float_type);
quote! {
#[cfg(feature = "serde")]
impl serde::Serialize for #struct_name
where
#float_type: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.value.serialize(serializer)
}
}
}
});
let deserialize_impls = for_all_constraint_float_types(config, |type_name, float_type, _| {
let struct_name = make_type_alias(type_name, float_type);
quote! {
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for #struct_name
where
#float_type: serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
// First deserialize the raw value
let value = #float_type::deserialize(deserializer)?;
// Then validate using the new() method
Self::new(value).map_err(|e| {
use serde::de::Error;
match e {
FloatError::NaN => D::Error::custom("value is NaN"),
FloatError::PosInf => D::Error::custom("value is positive infinity"),
FloatError::NegInf => D::Error::custom("value is negative infinity"),
FloatError::OutOfRange => D::Error::custom("value is out of range"),
FloatError::NoneOperand => D::Error::custom("none operand"),
}
})
}
}
}
});
quote! {
#(#serialize_impls)*
#(#deserialize_impls)*
}
}