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}