1use std::borrow::Cow;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum Kind {
7 Length,
9 Rank,
11 Index,
13 Limit,
15 Domain,
17 Overflow,
19 Interrupt,
21 Timeout,
23}
24
25macro_rules! reporters {
26 ($($kind:ident: $message:literal),+ $(,)?) => {
27 impl Kind {
28 pub fn reporter(self) -> extern "C" fn() {
29 match self { $(Self::$kind => {
30 extern "C" fn report() { record(Error::new(Kind::$kind, $message)); }
31 report
32 }),+ }
33 }
34 }
35 };
36}
37reporters! {
38 Length: "lengths do not agree", Rank: "argument has the wrong rank", Index: "read or write position is outside its source",
39 Limit: "array limit exceeded", Domain: "argument is outside the function's domain", Overflow: "integer arithmetic overflowed",
40 Interrupt: "computation interrupted", Timeout: "computation timed out",
41}
42
43#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct Error { pub kind: Kind, pub message: Cow<'static, str> }
45
46impl Error { pub fn new(kind: Kind, message: impl Into<Cow<'static, str>>) -> Self { Self { kind, message: message.into() } } }
47
48impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.message) } }
49
50impl std::error::Error for Error {}
51
52#[derive(Clone, Default)]
54pub struct ErrorSlot(std::sync::Arc<Errors>);
55
56#[derive(Default)]
57struct Errors { count: std::sync::atomic::AtomicU64, first: std::sync::Mutex<Option<Error>> }
58
59thread_local! { static ACTIVE: std::cell::RefCell<Option<ErrorSlot>> = const { std::cell::RefCell::new(None) }; }
60
61struct Restore(Option<ErrorSlot>);
62impl Drop for Restore { fn drop(&mut self) { ACTIVE.with(|active| active.replace(self.0.take())); } }
63
64impl ErrorSlot {
65 pub fn new() -> Self { Self::default() }
66 pub fn count(&self) -> u64 { self.0.count.load(std::sync::atomic::Ordering::Relaxed) }
67 pub fn pending(&self) -> bool { self.0.first.lock().unwrap().is_some() }
68 pub fn take(&self) -> Option<Error> { self.0.first.lock().unwrap().take() }
69 pub fn current() -> Option<Self> { ACTIVE.with(|active| active.borrow().clone()) }
71 pub fn within<R>(&self, run: impl FnOnce() -> R) -> R {
72 let (mut run, mut result) = (Some(run), None);
73 self.enter(&mut || result = run.take().map(|run| run()));
74 result.expect("the run ran")
75 }
76 fn enter(&self, run: &mut dyn FnMut()) { let _restore = Restore(ACTIVE.with(|active| active.replace(Some(self.clone())))); run() }
77}
78
79pub fn record(error: Error) {
81 ACTIVE.with(|active| {
82 if let Some(slot) = active.borrow().as_ref() {
83 slot.0.count.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
84 let mut first = slot.0.first.lock().unwrap();
85 if first.is_none() { *first = Some(error); }
86 }
87 });
88}
89
90pub fn checked<R>(run: impl FnOnce() -> Result<R, Error>) -> Result<R, Error> {
92 let (mut run, mut result) = (Some(run), None);
93 let recorded = recorded(&mut || result = run.take().map(|run| run()));
94 let result = result.expect("the run ran")?;
95 recorded.map_or(Ok(result), Err)
96}
97fn recorded(run: &mut dyn FnMut()) -> Option<Error> {
99 if ACTIVE.with(|active| active.borrow().is_some()) { run(); return None; }
100 thread_local! { static SPARE: ErrorSlot = ErrorSlot::new(); }
101 SPARE.with(|slot| {
102 slot.take();
103 slot.enter(run);
104 slot.take()
105 })
106}