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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! Everything related to errors.

use crate::wrappers::ptr::{
    array::dimensions::Dimensions,
    value::{Value, ValueRef},
};
use std::error::Error as StdErr;
use thiserror::Error;

pub(crate) static CANNOT_DISPLAY_TYPE: &'static str = "<Cannot display type>";
pub(crate) static CANNOT_DISPLAY_VALUE: &'static str = "<Cannot display value>";

/// Alias that is used for most `Result`s in this crate.
pub type JlrsResult<T> = Result<T, Box<JlrsError>>;

/// Julia result or exception.
///
/// Some functions from the Julia C API can throw exceptions. Many methods provided by jlrs will
/// catch these exceptions and return a `JuliaResult`. The `Ok` variant contains the result if no
/// exception has been thrown, otherwise the `Err` variant contains the exception.
pub type JuliaResult<'frame, 'data, V = Value<'frame, 'data>> = Result<V, Value<'frame, 'data>>;

/// Unrooted Julia result or exception.
///
/// This type alias is similar to [`JuliaResult`], but contains unrooted data rather than rooted
/// data.
pub type JuliaResultRef<'frame, 'data, V = ValueRef<'frame, 'data>> =
    Result<V, ValueRef<'frame, 'data>>;

/// Runtime errors.
#[derive(Debug, Error)]
pub enum RuntimeError {
    #[error("runtime can only be initialized once")]
    AlreadyInitialized,
    #[error("channel closed")]
    ChannelClosed,
    #[error("channel full")]
    ChannelFull,
}

/// IO errors.
#[derive(Debug, Error)]
pub enum IOError {
    #[error("path does not exist: {path}")]
    NotFound { path: String },
}

/// Type errors.
#[derive(Debug, Error)]
pub enum TypeError {
    #[error("expected a Function, {name} is a {type_str}")]
    NotAFunction { name: String, type_str: String },
    #[error("expected a NamedTuple, got a {type_str}")]
    NotANamedTuple { type_str: String },
    #[error("expected a Module, {name} is a {type_str}")]
    NotAModule { name: String, type_str: String },
    #[error("{element_type_str} is not a {value_type_str}")]
    IncompatibleType {
        element_type_str: String,
        value_type_str: String,
    },
    #[error("{value_type} is not subtype of {field_type}")]
    NotASubtype {
        value_type: String,
        field_type: String,
    },
}

/// Array layout errors.
#[derive(Debug, Error)]
pub enum ArrayLayoutError {
    #[error("element type is {element_type}, which is not a bits union")]
    NotUnion { element_type: String },
    #[error("element type is {element_type}, which is not stored inline")]
    NotInline { element_type: String },
    #[error("element type is {element_type}, which has pointer fields")]
    NotBits { element_type: String },
    #[error("element type is {element_type}, which is stored inline")]
    NotPointer { element_type: String },
}

/// Data access errors.
#[derive(Debug, Error)]
pub enum AccessError {
    #[error("{type_name} has no field named {field_name}")]
    NoSuchField {
        type_name: String,
        field_name: String,
    },
    #[error("layout is invalid for {value_type_str}")]
    InvalidLayout { value_type_str: String },
    #[error("no value named {name} in {module}")]
    GlobalNotFound { name: String, module: String },
    #[error("the current value is locked")]
    Locked,
    #[error("{tag} is not a valid tag for {union_type}")]
    IllegalUnionTag { union_type: String, tag: usize },
    #[error("field {field_name} of type {value_type} is not stored as a pointer")]
    NotAPointerField {
        value_type: String,
        field_name: String,
    },
    #[error("field at index {idx} does not exist: {value_type} has {n_fields} fields")]
    OutOfBoundsField {
        idx: usize,
        n_fields: usize,
        value_type: String,
    },
    #[error("index {idx} is out-of-bounds for SimpleVector of length {len}")]
    OutOfBoundsSVec { idx: usize, len: usize },
    #[error("index {idx} is invalid for array with shape {sz}")]
    InvalidIndex { idx: Dimensions, sz: Dimensions },
    #[error("arrays can only be accessed with n-dimensional indices")]
    ArrayNeedsNumericalIndex,
    #[error("fields cannot be accessed with n-dimensional indices")]
    FieldNeedsSimpleIndex,
    #[error("cannot access undefined reference")]
    UndefRef,
}

/// Data instantiation errors.
#[derive(Debug, Error)]
pub enum InstantiationError {
    #[error("cannot create array with DataType::instantiate")]
    ArrayNotSupported,
    #[error("NamedTuples must have an equal number of keys and values, got {n_names} keys and {n_values} values")]
    NamedTupleSizeMismatch { n_names: usize, n_values: usize },
    #[error("expected a shape for {vec_size} elements, got a shape for {dim_size} elements")]
    ArraySizeMismatch { dim_size: usize, vec_size: usize },
}

/// Allocation error.
#[derive(Debug, Error)]
pub enum AllocError {
    #[error("no free slots available, frame capacity: {cap} slots")]
    Full { cap: usize },
    #[error("cannot use NullFrame to root data")]
    NullFrame,
}

/// Julia exception converted to a string.
#[derive(Debug, Error)]
#[error("{msg}")]
pub struct Exception {
    msg: String,
}

impl Exception {
    /// Returns a reference to the error message.
    pub fn get_message(&self) -> &str {
        &self.msg
    }
}

/// All different errors.
#[derive(Debug, Error)]
pub enum JlrsError {
    #[error("Other: {0}")]
    Other(Box<dyn StdErr + 'static + Send + Sync>),
    #[error("Exception: {0}")]
    Exception(Exception),
    #[error("Runtime error: {0}")]
    RuntimeError(RuntimeError),
    #[error("Alloc error: {0}")]
    AllocError(AllocError),
    #[error("Type error: {0}")]
    TypeError(TypeError),
    #[error("IO error: {0}")]
    IOError(IOError),
    #[error("Access error: {0}")]
    AccessError(AccessError),
    #[error("Instantiation error: {0}")]
    InstantiationError(InstantiationError),
    #[error("Array layout error: {0}")]
    ArrayLayoutError(ArrayLayoutError),
}

impl JlrsError {
    /// Convert an arbitrary error to `JlrsError::Other`.
    pub fn other<E: StdErr + 'static + Send + Sync>(reason: E) -> Self {
        JlrsError::Other(Box::new(reason))
    }

    /// Convert an error message to `JlrsError::Exception`.
    pub fn exception(msg: String) -> Self {
        JlrsError::Exception(Exception { msg })
    }

    /// Convert an arbitrary error to `Err(JlrsError::Other)`.
    pub fn other_error<T, E: StdErr + 'static + Send + Sync>(reason: E) -> Result<T, Self> {
        Err(Self::other(reason))
    }

    /// Convert an error message to `Err(JlrsError::Exception)`.
    pub fn exception_error<T>(msg: String) -> Result<T, Self> {
        Err(JlrsError::exception(msg))
    }
}

macro_rules! impl_from {
    ($type:ident) => {
        impl From<$type> for JlrsError {
            fn from(e: $type) -> Self {
                JlrsError::$type(e)
            }
        }

        impl From<$type> for Box<JlrsError> {
            fn from(e: $type) -> Self {
                Box::new(JlrsError::from(e))
            }
        }
    };
}

impl_from!(RuntimeError);
impl_from!(AllocError);
impl_from!(TypeError);
impl_from!(IOError);
impl_from!(AccessError);
impl_from!(InstantiationError);
impl_from!(ArrayLayoutError);