ad_astra/analysis/
error.rs

1////////////////////////////////////////////////////////////////////////////////
2// This file is part of "Ad Astra", an embeddable scripting programming       //
3// language platform.                                                         //
4//                                                                            //
5// This work is proprietary software with source-available code.              //
6//                                                                            //
7// To copy, use, distribute, or contribute to this work, you must agree to    //
8// the terms of the General License Agreement:                                //
9//                                                                            //
10// https://github.com/Eliah-Lakhin/ad-astra/blob/master/EULA.md               //
11//                                                                            //
12// The agreement grants a Basic Commercial License, allowing you to use       //
13// this work in non-commercial and limited commercial products with a total   //
14// gross revenue cap. To remove this commercial limit for one of your         //
15// products, you must acquire a Full Commercial License.                      //
16//                                                                            //
17// If you contribute to the source code, documentation, or related materials, //
18// you must grant me an exclusive license to these contributions.             //
19// Contributions are governed by the "Contributions" section of the General   //
20// License Agreement.                                                         //
21//                                                                            //
22// Copying the work in parts is strictly forbidden, except as permitted       //
23// under the General License Agreement.                                       //
24//                                                                            //
25// If you do not or cannot agree to the terms of this Agreement,              //
26// do not use this work.                                                      //
27//                                                                            //
28// This work is provided "as is", without any warranties, express or implied, //
29// except where such disclaimers are legally invalid.                         //
30//                                                                            //
31// Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин).                 //
32// All rights reserved.                                                       //
33////////////////////////////////////////////////////////////////////////////////
34
35use std::{
36    error::Error,
37    fmt::{Display, Formatter},
38};
39
40use lady_deirdre::{
41    analysis::{AnalysisError, AnalysisResult},
42    arena::{Id, Identifiable},
43};
44
45use crate::report::system_panic;
46
47/// An alias type for analysis results.
48pub type ModuleResult<T> = Result<T, ModuleError>;
49
50/// An error type for script module analysis.
51///
52/// Some variants of this enum (such as [Cursor](ModuleError::Cursor)) represent
53/// errors indicating that the arguments supplied to the function are not
54/// valid. Other variants indicate that the result cannot be computed due to
55/// specific reasons.
56#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
57#[non_exhaustive]
58pub enum ModuleError {
59    /// Indicates that the function cannot fulfill the request because another
60    /// thread is attempting to revoke the current read or write access to this
61    /// script module in a multi-threaded environment.
62    ///
63    /// If you encounter this error in a worker thread, you should drop the
64    /// module's [read](crate::analysis::ModuleReadGuard) or
65    /// [write](crate::analysis::ModuleWriteGuard) access object to give
66    /// priority to another thread. After dropping the access object, it is
67    /// recommended to pause the worker thread for a short amount of time before
68    /// retrying the operation by acquiring a new module access guard.
69    ///
70    /// In single-threaded programs, this error should never occur unless access
71    /// handles are manually triggered.
72    ///
73    /// See the [ScriptModule](crate::analysis::ScriptModule) documentation for
74    /// more details about multi-threaded analysis tools.
75    Interrupted(Id),
76
77    /// Indicates that the analysis operation cannot be completed within
78    /// the predefined amount of time.
79    ///
80    /// This type of error is rare and may occur only in specific edge cases.
81    /// The internal semantic analysis algorithm allocates generous timeout
82    /// limits for semantic operations, which should be sufficient to handle a
83    /// wide range of semantic requests in large source code texts, even on
84    /// low-end machines. However, if a request operation takes too long, the
85    /// analyzer may decide to give up and return a Timeout error.
86    ///
87    /// In such cases, rerunning the operation typically has no benefit, so you
88    /// can either display an error message to the user or silently ignore the
89    /// request. If implementing a hand-written LSP server, you may choose to
90    /// return an empty response to the language client.
91    Timeout(Id),
92
93    /// Indicates that the addressed source code character or range of
94    /// characters is not valid for the underlying script module.
95    Cursor(Id),
96}
97
98impl Error for ModuleError {}
99
100impl Identifiable for ModuleError {
101    #[inline(always)]
102    fn id(&self) -> Id {
103        match self {
104            Self::Interrupted(id) => *id,
105            Self::Timeout(id) => *id,
106            Self::Cursor(id) => *id,
107        }
108    }
109}
110
111impl Display for ModuleError {
112    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
113        match self {
114            Self::Interrupted(id) => formatter.write_fmt(format_args!(
115                "Cannot complete module {id} analysis request because the \
116                operation was interrupted.",
117            )),
118
119            Self::Timeout(id) => {
120                formatter.write_fmt(format_args!("Module {id} analysis request timed out.",))
121            }
122
123            Self::Cursor(id) => formatter.write_fmt(format_args!(
124                "The specified source code site or range of sites is not valid for module {id}.",
125            )),
126        }
127    }
128}
129
130pub(crate) trait ModuleResultEx<T>: Sized {
131    fn into_module_result(self, id: Id) -> ModuleResult<T>;
132
133    fn forward(self) -> AnalysisResult<T>;
134}
135
136impl<T> ModuleResultEx<T> for AnalysisResult<T> {
137    #[track_caller]
138    #[inline(always)]
139    fn into_module_result(self, id: Id) -> ModuleResult<T> {
140        match self {
141            Ok(ok) => Ok(ok),
142            Err(error) => match error {
143                AnalysisError::Interrupted => Err(ModuleError::Interrupted(id)),
144                AnalysisError::Timeout if cfg!(not(debug_assertions)) => {
145                    Err(ModuleError::Timeout(id))
146                }
147                _ => system_panic!("Analysis internal error. {error}",),
148            },
149        }
150    }
151
152    #[track_caller]
153    #[inline(always)]
154    fn forward(self) -> AnalysisResult<T> {
155        match self {
156            Ok(ok) => Ok(ok),
157            Err(error) if !error.is_abnormal() => Err(error),
158            Err(error) => system_panic!("Analysis internal error. {error}",),
159        }
160    }
161}