tupa_core/lib.rs
1//! Tupã core library — types, traits, and pipeline macro re-export.
2
3// Allow `tupa_core::...` paths (emitted by our proc-macros) to resolve within
4// this crate itself, so the macros work in unit tests and doctests alike.
5extern crate self as tupa_core;
6
7/// Re-export the procedural macro so users can write `use tupa_core::pipeline;`
8pub use tupa_core_macros::pipeline;
9
10/// Re-export the `safe!` macro for compile-time-proven constrained values.
11///
12/// `safe!(Marker, expr)` proves the constraint at compile time when `expr` is a
13/// constant `f64` expression, or falls back to a runtime check otherwise.
14///
15/// ```
16/// use tupa_core::{safe, constraints::NonNan};
17/// // Proven at compile time (constant expression):
18/// let ok = safe!(NonNan, 1.0 + 2.0);
19/// assert_eq!(ok.get(), 3.0);
20/// // Runtime-checked for a non-constant value:
21/// let x = "0.5".parse::<f64>().unwrap();
22/// let y = safe!(NonNan, x);
23/// assert_eq!(y.get(), 0.5);
24/// ```
25///
26/// A constant expression that violates the constraint fails to compile:
27///
28/// ```compile_fail
29/// use tupa_core::{safe, constraints::NonNan};
30/// let bad = safe!(NonNan, 0.0_f64 / 0.0_f64); // NaN — E3002 at compile time
31/// ```
32pub use tupa_core_macros::safe;
33
34/// Re-export serde_json for generated code.
35pub use serde_json;
36
37// ============================================================================
38// Core types
39// ============================================================================
40
41/// A value with a compile-time proven constraint marker `C`.
42///
43/// The constraint type `C` is a zero-sized marker. Example:
44/// ```rust,ignore
45/// type X = Safe<f64, !nan>;
46/// ```
47#[derive(Clone, Copy, PartialEq, Eq, Debug)]
48pub struct Safe<T, C>(pub T, std::marker::PhantomData<C>);
49
50impl<T, C> Safe<T, C> {
51 /// Create a new `Safe` value (no constraint check).
52 ///
53 /// Prefer [`Safe::try_new`] (runtime-checked) or the [`crate::safe!`] macro
54 /// (compile-time proof for constant expressions) when the constraint `C`
55 /// implements [`Constraint`].
56 pub fn new(value: T) -> Self {
57 Safe(value, std::marker::PhantomData)
58 }
59
60 /// Create a `Safe` without checking the constraint.
61 ///
62 /// Use only when the constraint is already established by construction
63 /// (e.g. proven at compile time by the [`crate::safe!`] macro).
64 pub fn new_unchecked(value: T) -> Self {
65 Safe(value, std::marker::PhantomData)
66 }
67
68 /// Extract the inner value.
69 pub fn into_inner(self) -> T {
70 self.0
71 }
72}
73
74impl<T, C> Safe<T, C>
75where
76 C: Constraint<T>,
77{
78 /// Construct a `Safe`, checking the constraint `C` at runtime.
79 ///
80 /// Returns [`ConstraintError`] if `value` does not satisfy `C`.
81 pub fn try_new(value: T) -> Result<Self, ConstraintError> {
82 if C::satisfied(&value) {
83 Ok(Safe(value, std::marker::PhantomData))
84 } else {
85 Err(ConstraintError {
86 constraint: C::NAME,
87 })
88 }
89 }
90}
91
92impl<T, C> Safe<T, C>
93where
94 T: Copy,
95{
96 /// Get a reference to the inner value.
97 pub fn get(&self) -> T {
98 self.0
99 }
100
101 /// Map the inner value to a new `Safe` with a different constraint marker.
102 pub fn map<U, F>(self, f: F) -> Safe<U, C>
103 where
104 F: FnOnce(T) -> U,
105 {
106 Safe::new(f(self.0))
107 }
108}
109
110/// Arithmetic operators for Safe<T, C>
111macro_rules! impl_safe_arith {
112 ($trait:ident, $method:ident, $assign_trait:ident, $assign_method:ident) => {
113 impl<T, C> std::ops::$trait for Safe<T, C>
114 where
115 T: std::ops::$trait<Output = T> + Copy,
116 {
117 type Output = Safe<T, C>;
118
119 fn $method(self, rhs: Self) -> Self::Output {
120 Safe::new(self.0.$method(rhs.0))
121 }
122 }
123 };
124}
125
126impl_safe_arith!(Add, add, AddAssign, add_assign);
127impl_safe_arith!(Sub, sub, SubAssign, sub_assign);
128impl_safe_arith!(Mul, mul, MulAssign, mul_assign);
129impl_safe_arith!(Div, div, DivAssign, div_assign);
130
131// Support RHS as raw value
132impl<T, C> std::ops::Add<T> for Safe<T, C>
133where
134 T: std::ops::Add<Output = T> + Copy,
135{
136 type Output = Safe<T, C>;
137 fn add(self, rhs: T) -> Self::Output {
138 Safe::new(self.0 + rhs)
139 }
140}
141
142impl<T, C> std::ops::Sub<T> for Safe<T, C>
143where
144 T: std::ops::Sub<Output = T> + Copy,
145{
146 type Output = Safe<T, C>;
147 fn sub(self, rhs: T) -> Self::Output {
148 Safe::new(self.0 - rhs)
149 }
150}
151
152impl<T, C> std::ops::Mul<T> for Safe<T, C>
153where
154 T: std::ops::Mul<Output = T> + Copy,
155{
156 type Output = Safe<T, C>;
157 fn mul(self, rhs: T) -> Self::Output {
158 Safe::new(self.0 * rhs)
159 }
160}
161
162impl<T, C> std::ops::Div<T> for Safe<T, C>
163where
164 T: std::ops::Div<Output = T> + Copy,
165{
166 type Output = Safe<T, C>;
167 fn div(self, rhs: T) -> Self::Output {
168 Safe::new(self.0 / rhs)
169 }
170}
171
172/// Negation for Safe<T, C>
173impl<T, C> std::ops::Neg for Safe<T, C>
174where
175 T: std::ops::Neg<Output = T> + Copy,
176{
177 type Output = Safe<T, C>;
178 fn neg(self) -> Self::Output {
179 Safe::new(-self.0)
180 }
181}
182
183// ============================================================================
184// Constraints (alignment types) — experimental
185// ============================================================================
186
187/// A constraint that a [`Safe<T, C>`] value must satisfy.
188///
189/// Implement this for a zero-sized marker type `C` to make it usable with
190/// [`Safe::try_new`] and the [`crate::safe!`] macro.
191pub trait Constraint<T> {
192 /// Stable, human-readable identifier (e.g. `"!nan"`).
193 const NAME: &'static str;
194
195 /// Returns `true` if `value` satisfies the constraint.
196 fn satisfied(value: &T) -> bool;
197}
198
199/// Error returned by [`Safe::try_new`] when a value violates its [`Constraint`].
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct ConstraintError {
202 /// The [`Constraint::NAME`] that was violated.
203 pub constraint: &'static str,
204}
205
206impl std::fmt::Display for ConstraintError {
207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208 write!(f, "value violates constraint `{}`", self.constraint)
209 }
210}
211
212impl std::error::Error for ConstraintError {}
213
214/// Built-in constraint marker types.
215///
216/// These are kept in a submodule (not re-exported at the crate root) so they do
217/// not collide with user-defined markers.
218pub mod constraints {
219 use super::Constraint;
220
221 /// `!nan` — the value is not `NaN`.
222 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
223 pub struct NonNan;
224
225 impl Constraint<f64> for NonNan {
226 const NAME: &'static str = "!nan";
227 fn satisfied(value: &f64) -> bool {
228 !value.is_nan()
229 }
230 }
231
232 /// `!inf` — the value is not `±∞`.
233 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
234 pub struct NonInf;
235
236 impl Constraint<f64> for NonInf {
237 const NAME: &'static str = "!inf";
238 fn satisfied(value: &f64) -> bool {
239 !value.is_infinite()
240 }
241 }
242
243 /// `!nan, !inf` — the value is finite.
244 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
245 pub struct Finite;
246
247 impl Constraint<f64> for Finite {
248 const NAME: &'static str = "!nan,!inf";
249 fn satisfied(value: &f64) -> bool {
250 value.is_finite()
251 }
252 }
253}
254
255/// Tensor placeholder with shape information.
256///
257/// In full implementation, shape will be encoded as const generics.
258/// For Sprint 1, this is a simple wrapper.
259#[derive(Debug, Clone, Copy, PartialEq)]
260pub struct Tensor<T>(pub T);
261
262impl<T> Tensor<T> {
263 /// Create a new Tensor.
264 pub fn new(value: T) -> Self {
265 Tensor(value)
266 }
267
268 /// Extract the inner value.
269 pub fn into_inner(self) -> T {
270 self.0
271 }
272}
273
274impl<T> Tensor<T>
275where
276 T: Copy,
277{
278 /// Get a reference to the inner value.
279 pub fn get(&self) -> T {
280 self.0
281 }
282}
283
284// ============================================================================
285// Pipeline trait
286// ============================================================================
287
288/// Trait implemented by all pipelines (generated by the `pipeline!` macro).
289///
290/// You should not implement this manually.
291pub trait Pipeline {
292 /// The input data type for this pipeline.
293 type Input: Send + Sync + 'static;
294
295 /// Returns the human-readable name of the pipeline.
296 fn name(&self) -> &'static str;
297}
298
299/// Test-only pipeline for compile-time trait bound verification.
300#[derive(Debug, Clone)]
301pub struct TestPipeline;
302
303impl Pipeline for TestPipeline {
304 type Input = i32;
305 fn name(&self) -> &'static str {
306 "TestPipeline"
307 }
308}
309
310#[cfg(test)]
311mod tests;