Skip to main content

tenferro_tensor/
error.rs

1//! Runtime error types for tensor execution.
2//!
3//! # Examples
4//!
5//! ```rust
6//! let error = tenferro_tensor::Error::shape_mismatch("add", [2], [3]);
7//! assert!(matches!(
8//!     error,
9//!     tenferro_tensor::Error::Validation { op: "add", .. }
10//! ));
11//! ```
12
13use std::error::Error as StdError;
14
15use tenferro_tensor_core::{ErrorKind, ValidationError};
16
17/// Boxed source used for backend and extension failures whose concrete type is
18/// owned by another crate or a vendor API.
19pub type BoxError = Box<dyn StdError + Send + Sync + 'static>;
20
21/// Runtime failures produced by tensor execution backends and helpers.
22///
23/// Validation failures retain the shared tensor-core payload as a typed source.
24/// Backend and extension failures retain opaque typed sources when one exists;
25/// text-only vendor failures use [`Error::BackendFailure`].
26///
27/// # Examples
28///
29/// ```rust
30/// let error = tenferro_tensor::Error::rank_mismatch("reshape", 2, 1);
31/// assert!(matches!(
32///     error,
33///     tenferro_tensor::Error::Validation { op: "reshape", .. }
34/// ));
35/// ```
36#[derive(Debug, thiserror::Error)]
37#[non_exhaustive]
38pub enum Error {
39    #[error("{op}: {source}")]
40    Validation {
41        op: &'static str,
42        #[source]
43        source: ValidationError,
44    },
45    #[error("{op}: unsupported dtype conversion from {from:?} to {to:?}: {message}")]
46    UnsupportedDTypeConversion {
47        op: &'static str,
48        from: crate::DType,
49        to: crate::DType,
50        message: String,
51    },
52    #[error("{op}: unsupported dtype {dtype:?}: {message}")]
53    UnsupportedDType {
54        op: &'static str,
55        dtype: crate::DType,
56        message: String,
57    },
58    #[error("{op}: unsupported operation: {message}")]
59    Unsupported { op: &'static str, message: String },
60    #[error("{op}: backend failure: {message}")]
61    BackendFailure { op: &'static str, message: String },
62    #[error("{op}: backend failure: {source}")]
63    BackendSource {
64        op: &'static str,
65        #[source]
66        source: BoxError,
67    },
68    #[error("{op}: I/O failure: {source}")]
69    IoSource {
70        op: &'static str,
71        #[source]
72        source: BoxError,
73    },
74    #[error("{op}: runtime state failure: {message}")]
75    RuntimeState { op: &'static str, message: String },
76    #[error("{op}: runtime state failure: {source}")]
77    RuntimeStateSource {
78        op: &'static str,
79        #[source]
80        source: BoxError,
81    },
82    #[error("{op}: host access failed: {source}")]
83    HostAccess {
84        op: &'static str,
85        #[source]
86        source: crate::HostAccessError,
87    },
88    #[error("{op}: extension {family} failed: {source}")]
89    Extension {
90        op: &'static str,
91        family: &'static str,
92        kind: ErrorKind,
93        #[source]
94        source: BoxError,
95    },
96    #[error("missing runtime value for slot {slot}")]
97    MissingValue { slot: usize },
98    #[error("internal tensor error: {0}")]
99    Internal(String),
100}
101
102impl Error {
103    /// Construct an incompatible-shapes validation error.
104    ///
105    /// # Examples
106    ///
107    /// ```rust
108    /// use tenferro_tensor::Error;
109    ///
110    /// let error = Error::shape_mismatch("add", [2, 3], [2, 4]);
111    /// assert!(matches!(error, Error::Validation { .. }));
112    /// ```
113    pub fn shape_mismatch(
114        op: &'static str,
115        lhs: impl Into<Vec<usize>>,
116        rhs: impl Into<Vec<usize>>,
117    ) -> Self {
118        Self::validation(
119            op,
120            tenferro_tensor_core::ShapeMismatch::IncompatibleShapes {
121                lhs: tenferro_tensor_core::ShapeVec::from_vec(lhs.into()),
122                rhs: tenferro_tensor_core::ShapeVec::from_vec(rhs.into()),
123            }
124            .into(),
125        )
126    }
127
128    /// Construct a rank-mismatch validation error.
129    ///
130    /// # Examples
131    ///
132    /// ```rust
133    /// use tenferro_tensor::Error;
134    ///
135    /// let error = Error::rank_mismatch("transpose", 2, 3);
136    /// assert!(matches!(error, Error::Validation { .. }));
137    /// ```
138    pub fn rank_mismatch(op: &'static str, expected: usize, actual: usize) -> Self {
139        Self::validation(op, ValidationError::RankMismatch { expected, actual })
140    }
141
142    /// Construct an axis-out-of-bounds validation error.
143    ///
144    /// # Examples
145    ///
146    /// ```rust
147    /// use tenferro_tensor::Error;
148    ///
149    /// let error = Error::axis_out_of_bounds("sum", 2, 2);
150    /// assert!(matches!(error, Error::Validation { .. }));
151    /// ```
152    pub fn axis_out_of_bounds(op: &'static str, axis: usize, rank: usize) -> Self {
153        Self::validation(op, ValidationError::AxisOutOfBounds { axis, rank })
154    }
155
156    /// Construct a duplicate-axis validation error.
157    ///
158    /// # Examples
159    ///
160    /// ```rust
161    /// use tenferro_tensor::Error;
162    ///
163    /// let error = Error::duplicate_axis("transpose", 1, "permutation");
164    /// assert!(matches!(error, Error::Validation { .. }));
165    /// ```
166    pub fn duplicate_axis(op: &'static str, axis: usize, role: &'static str) -> Self {
167        Self::validation(op, ValidationError::DuplicateAxis { axis, role })
168    }
169
170    /// Construct a dtype-mismatch validation error.
171    ///
172    /// # Examples
173    ///
174    /// ```rust
175    /// use tenferro_tensor::{DType, Error};
176    ///
177    /// let error = Error::dtype_mismatch("add", DType::F32, DType::F64);
178    /// assert!(matches!(error, Error::Validation { .. }));
179    /// ```
180    pub fn dtype_mismatch(op: &'static str, expected: crate::DType, actual: crate::DType) -> Self {
181        Self::validation(
182            op,
183            ValidationError::DTypeMismatch {
184                expected: crate::core_dtype(expected),
185                actual: crate::core_dtype(actual),
186            },
187        )
188    }
189
190    /// Wrap shared tensor validation with the operation that requested it.
191    ///
192    /// # Examples
193    ///
194    /// ```rust
195    /// use tenferro_tensor::{Error, ValidationError};
196    ///
197    /// let error = Error::validation(
198    ///     "transpose",
199    ///     ValidationError::AxisOutOfBounds { axis: 2, rank: 2 },
200    /// );
201    /// assert!(matches!(error, Error::Validation { op: "transpose", .. }));
202    /// ```
203    pub fn validation(op: &'static str, source: ValidationError) -> Self {
204        Self::Validation { op, source }
205    }
206
207    /// Construct a structured invalid-argument validation error.
208    ///
209    /// # Examples
210    ///
211    /// ```rust
212    /// use tenferro_tensor::{Error, ErrorKind, ValidationKind};
213    ///
214    /// let error = Error::invalid_argument("slice", "step", "must be non-zero");
215    /// assert_eq!(error.kind(), ErrorKind::Validation(ValidationKind::InvalidArgument));
216    /// ```
217    pub fn invalid_argument(
218        op: &'static str,
219        argument: &'static str,
220        message: impl Into<String>,
221    ) -> Self {
222        Self::validation(
223            op,
224            ValidationError::InvalidArgument {
225                argument,
226                message: message.into(),
227            },
228        )
229    }
230
231    /// Construct an unsupported dtype conversion error.
232    ///
233    /// # Examples
234    ///
235    /// ```rust
236    /// let error = tenferro_tensor::Error::unsupported_dtype_conversion(
237    ///     "convert",
238    ///     tenferro_tensor::DType::F64,
239    ///     tenferro_tensor::DType::I32,
240    ///     "lossy conversion is disabled",
241    /// );
242    /// assert!(matches!(
243    ///     error,
244    ///     tenferro_tensor::Error::UnsupportedDTypeConversion { .. }
245    /// ));
246    /// ```
247    pub fn unsupported_dtype_conversion(
248        op: &'static str,
249        from: crate::DType,
250        to: crate::DType,
251        message: impl Into<String>,
252    ) -> Self {
253        Self::UnsupportedDTypeConversion {
254            op,
255            from,
256            to,
257            message: message.into(),
258        }
259    }
260
261    /// Construct an operation-level unsupported-dtype error.
262    ///
263    /// This is for an operation that cannot run for the supplied dtype. It is
264    /// deliberately distinct from [`Error::unsupported_dtype_conversion`],
265    /// which is reserved for an actual from-dtype to to-dtype conversion.
266    ///
267    /// # Examples
268    ///
269    /// ```rust
270    /// let error = tenferro_tensor::Error::unsupported_dtype(
271    ///     "exp",
272    ///     tenferro_tensor::DType::I64,
273    ///     "integer exponentials are not implemented",
274    /// );
275    /// assert!(matches!(
276    ///     error,
277    ///     tenferro_tensor::Error::UnsupportedDType {
278    ///         op: "exp",
279    ///         dtype: tenferro_tensor::DType::I64,
280    ///         ..
281    ///     }
282    /// ));
283    /// ```
284    pub fn unsupported_dtype(
285        op: &'static str,
286        dtype: crate::DType,
287        message: impl Into<String>,
288    ) -> Self {
289        Self::UnsupportedDType {
290            op,
291            dtype,
292            message: message.into(),
293        }
294    }
295
296    /// Construct a structured unsupported-operation error.
297    ///
298    /// Use this for an operation or execution surface that is not implemented
299    /// by the selected backend. Dtype conversion failures use
300    /// [`Error::unsupported_dtype_conversion`] instead, and operation-specific
301    /// typed reasons should use [`Error::extension`] with `ErrorKind::Unsupported`.
302    ///
303    /// # Examples
304    ///
305    /// ```rust
306    /// let error = tenferro_tensor::Error::unsupported(
307    ///     "full_piv_lu",
308    ///     "backend has no implementation",
309    /// );
310    /// assert!(matches!(
311    ///     error,
312    ///     tenferro_tensor::Error::Unsupported { op: "full_piv_lu", .. }
313    /// ));
314    /// ```
315    pub fn unsupported(op: &'static str, message: impl Into<String>) -> Self {
316        Self::Unsupported {
317            op,
318            message: message.into(),
319        }
320    }
321
322    /// Construct a text-only backend failure.
323    ///
324    /// Use [`Error::backend_source`] when a typed source is available.
325    ///
326    /// # Examples
327    ///
328    /// ```rust
329    /// let error = tenferro_tensor::Error::backend_failure(
330    ///     "matmul",
331    ///     "backend rejected launch",
332    /// );
333    /// assert!(matches!(
334    ///     error,
335    ///     tenferro_tensor::Error::BackendFailure { op: "matmul", .. }
336    /// ));
337    /// ```
338    pub fn backend_failure(op: &'static str, message: impl Into<String>) -> Self {
339        Self::BackendFailure {
340            op,
341            message: message.into(),
342        }
343    }
344
345    /// Construct a backend failure while preserving its typed source.
346    ///
347    /// # Examples
348    ///
349    /// ```rust
350    /// let error = tenferro_tensor::Error::backend_source(
351    ///     "load",
352    ///     std::io::Error::other("read failed"),
353    /// );
354    /// assert!(std::error::Error::source(&error).is_some());
355    /// ```
356    pub fn backend_source<E>(op: &'static str, source: E) -> Self
357    where
358        E: StdError + Send + Sync + 'static,
359    {
360        Self::BackendSource {
361            op,
362            source: Box::new(source),
363        }
364    }
365
366    /// Construct an I/O failure while preserving its typed source.
367    ///
368    /// I/O errors are intentionally separate from backend failures: callers
369    /// can classify them as [`ErrorKind::Io`] without parsing a message.
370    ///
371    /// # Examples
372    ///
373    /// ```rust
374    /// use tenferro_tensor::{Error, ErrorKind};
375    ///
376    /// let error = Error::io_source("load", std::io::Error::other("read failed"));
377    /// assert_eq!(error.kind(), ErrorKind::Io);
378    /// assert!(std::error::Error::source(&error).is_some());
379    /// ```
380    pub fn io_source<E>(op: &'static str, source: E) -> Self
381    where
382        E: StdError + Send + Sync + 'static,
383    {
384        Self::IoSource {
385            op,
386            source: Box::new(source),
387        }
388    }
389
390    /// Construct a runtime-state failure when no typed source exists.
391    ///
392    /// Use this for missing, uninitialized, or invalid execution state. It is
393    /// distinct from [`Error::backend_failure`], which is reserved for
394    /// vendor/backend status text.
395    ///
396    /// # Examples
397    ///
398    /// ```rust
399    /// use tenferro_tensor::{Error, ErrorKind};
400    ///
401    /// let error = Error::runtime_state("execute", "backend session is not initialized");
402    /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
403    /// ```
404    pub fn runtime_state(op: &'static str, message: impl Into<String>) -> Self {
405        Self::RuntimeState {
406            op,
407            message: message.into(),
408        }
409    }
410
411    /// Construct a runtime-state failure while preserving a typed source.
412    ///
413    /// # Examples
414    ///
415    /// ```rust
416    /// use tenferro_tensor::{Error, ErrorKind};
417    ///
418    /// let error = Error::runtime_state_source(
419    ///     "execute",
420    ///     std::io::Error::other("executor lock poisoned"),
421    /// );
422    /// assert_eq!(error.kind(), ErrorKind::RuntimeState);
423    /// assert!(std::error::Error::source(&error).is_some());
424    /// ```
425    pub fn runtime_state_source<E>(op: &'static str, source: E) -> Self
426    where
427        E: StdError + Send + Sync + 'static,
428    {
429        Self::RuntimeStateSource {
430            op,
431            source: Box::new(source),
432        }
433    }
434
435    /// Construct an extension failure while preserving its typed source and
436    /// coarse classification.
437    ///
438    /// # Examples
439    ///
440    /// ```rust
441    /// use std::error::Error as _;
442    /// use tenferro_tensor::{Error, ErrorKind};
443    ///
444    /// let error = Error::extension(
445    ///     "einsum",
446    ///     "einsum",
447    ///     ErrorKind::Internal,
448    ///     std::io::Error::other("planner failed"),
449    /// );
450    /// assert!(error.source().is_some());
451    /// ```
452    pub fn extension<E>(op: &'static str, family: &'static str, kind: ErrorKind, source: E) -> Self
453    where
454        E: StdError + Send + Sync + 'static,
455    {
456        Self::Extension {
457            op,
458            family,
459            kind,
460            source: Box::new(source),
461        }
462    }
463
464    /// Preserve a typed guarded-host-access failure.
465    ///
466    /// # Examples
467    ///
468    /// ```rust
469    /// use tenferro_tensor::{Error, HostAccessError};
470    ///
471    /// let error = Error::host_access(
472    ///     "map",
473    ///     HostAccessError::Unsupported { backend: "opaque" },
474    /// );
475    /// assert!(matches!(error, Error::HostAccess { .. }));
476    /// ```
477    pub fn host_access(op: &'static str, source: crate::HostAccessError) -> Self {
478        Self::HostAccess { op, source }
479    }
480
481    /// Return the stable coarse classification for this tensor failure.
482    ///
483    /// # Examples
484    ///
485    /// ```rust
486    /// use tenferro_tensor::{Error, ErrorKind, ValidationError, ValidationKind};
487    /// use tenferro_tensor::core::DType;
488    ///
489    /// let error = Error::validation(
490    ///     "add",
491    ///     ValidationError::DTypeMismatch {
492    ///         expected: DType::F32,
493    ///         actual: DType::F64,
494    ///     },
495    /// );
496    /// assert_eq!(error.kind(), ErrorKind::Validation(ValidationKind::DTypeMismatch));
497    /// ```
498    pub fn kind(&self) -> ErrorKind {
499        match self {
500            Self::Validation { source, .. } => ErrorKind::Validation(source.kind()),
501            Self::UnsupportedDTypeConversion { .. }
502            | Self::UnsupportedDType { .. }
503            | Self::Unsupported { .. } => ErrorKind::Unsupported,
504            Self::BackendFailure { .. } | Self::BackendSource { .. } => ErrorKind::BackendFailure,
505            Self::IoSource { .. } => ErrorKind::Io,
506            Self::RuntimeState { .. }
507            | Self::RuntimeStateSource { .. }
508            | Self::HostAccess { .. } => ErrorKind::RuntimeState,
509            Self::Extension { kind, .. } => *kind,
510            Self::MissingValue { .. } => ErrorKind::RuntimeState,
511            Self::Internal(_) => ErrorKind::Internal,
512        }
513    }
514}
515
516/// Result type alias for runtime tensor operations.
517pub type Result<T> = std::result::Result<T, Error>;