Skip to main content

runmat_runtime/
runtime_error.rs

1pub use runmat_async::{
2    runtime_error as build_runtime_error, CallFrame, ErrorContext, GpuGatherRetry, RuntimeError,
3    RuntimeErrorBuilder,
4};
5
6/// Construct a language-semantic runtime error in the active session's
7/// namespace. Executors may attach frame and source-span information, but the
8/// identifier and message are executor-neutral runtime semantics.
9pub fn semantic_error(identifier: &str, message: impl Into<String>) -> RuntimeError {
10    let suffix = identifier
11        .split_once(':')
12        .map_or(identifier, |(_, suffix)| suffix);
13    let namespace =
14        crate::context::legacy::error_namespace().unwrap_or_else(|| "RunMat".to_string());
15    build_runtime_error(message)
16        .with_identifier(format!("{namespace}:{suffix}"))
17        .build()
18}
19
20/// Materialize the language-level exception value caught by `try`/`catch`.
21///
22/// Executors own control transfer and frame/source decoration; Runtime owns
23/// the stable conversion from its semantic error contract to `MException`.
24pub fn exception_from_error(error: &RuntimeError) -> runmat_value::MException {
25    if let Some(identifier) = error.identifier() {
26        return runmat_value::MException::new(identifier.to_string(), error.message().to_string());
27    }
28    let message = error.message();
29    if let Some(index) = message.rfind(": ") {
30        let (identifier, detail) = message.split_at(index);
31        return runmat_value::MException::new(
32            exception_identifier(identifier),
33            detail.trim_start_matches(':').trim().to_string(),
34        );
35    }
36    if let Some(index) = message.rfind(':') {
37        let (identifier, detail) = message.split_at(index);
38        return runmat_value::MException::new(
39            exception_identifier(identifier),
40            detail.trim_start_matches(':').trim().to_string(),
41        );
42    }
43    runmat_value::MException::new(exception_identifier(""), message.to_string())
44}
45
46fn exception_identifier(identifier: &str) -> String {
47    if identifier.trim().is_empty() {
48        let namespace =
49            crate::context::legacy::error_namespace().unwrap_or_else(|| "RunMat".to_string());
50        format!("{namespace}:error")
51    } else {
52        identifier.trim().to_string()
53    }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum ReplayErrorKind {
58    UnsupportedSchema,
59    PayloadTooLarge,
60    DecodeFailed,
61    ExportRejected,
62    ImportRejected,
63}
64
65impl ReplayErrorKind {
66    pub fn identifier(self) -> &'static str {
67        match self {
68            Self::UnsupportedSchema => "RunMat:ReplayUnsupportedSchema",
69            Self::PayloadTooLarge => "RunMat:ReplayPayloadTooLarge",
70            Self::DecodeFailed => "RunMat:ReplayDecodeFailed",
71            Self::ExportRejected => "RunMat:ReplayExportRejected",
72            Self::ImportRejected => "RunMat:ReplayImportRejected",
73        }
74    }
75}
76
77pub fn replay_error(kind: ReplayErrorKind, message: impl Into<String>) -> RuntimeError {
78    build_runtime_error(message)
79        .with_builtin("replay")
80        .with_identifier(kind.identifier())
81        .build()
82}
83
84pub fn replay_error_with_source(
85    kind: ReplayErrorKind,
86    message: impl Into<String>,
87    source: impl std::error::Error + Send + Sync + 'static,
88) -> RuntimeError {
89    build_runtime_error(message)
90        .with_builtin("replay")
91        .with_identifier(kind.identifier())
92        .with_source(source)
93        .build()
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use crate::context::{RuntimeContext, RuntimeContextGuard};
100    use crate::execution::RuntimeExecutionService;
101    use std::rc::Rc;
102
103    #[test]
104    fn semantic_errors_use_the_active_session_namespace() {
105        let context = RuntimeContext::new(Rc::new(RuntimeExecutionService::new()));
106        context.set_error_namespace("Acme");
107        let _scope = RuntimeContextGuard::enter(context);
108
109        assert_eq!(
110            semantic_error("MATLAB:badsubscript", "bad index").identifier(),
111            Some("Acme:badsubscript")
112        );
113        assert_eq!(
114            semantic_error("IndexOutOfBounds", "bad index").identifier(),
115            Some("Acme:IndexOutOfBounds")
116        );
117    }
118
119    #[test]
120    fn caught_exception_materialization_preserves_structured_and_legacy_errors() {
121        let structured = semantic_error("IndexOutOfBounds", "bad index");
122        let exception = exception_from_error(&structured);
123        assert_eq!(exception.identifier, "RunMat:IndexOutOfBounds");
124        assert_eq!(exception.message, "bad index");
125
126        let context = RuntimeContext::new(Rc::new(RuntimeExecutionService::new()));
127        context.set_error_namespace("Acme");
128        let _scope = RuntimeContextGuard::enter(context);
129        let legacy = crate::build_runtime_error("legacy detail").build();
130        let exception = exception_from_error(&legacy);
131        assert_eq!(exception.identifier, "Acme:error");
132        assert_eq!(exception.message, "legacy detail");
133    }
134}