Skip to main content

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;