vb6semantic/error.rs
1//! Error types for semantic analysis
2//!
3//! This module defines the `SemanticError` enum, which represents
4//! various kinds of errors that can occur during semantic analysis
5//! of VB6 code. Each variant includes relevant information about
6//! the error, such as the symbol name, expected and found types,
7//! and source location. The `SourceLocation` struct provides a
8//! standardized way to represent the location of errors in the
9//! source code.
10//!
11//! The `Result` type alias is defined for convenience, allowing functions
12//! to return `Result<T, SemanticError>` without needing to specify the
13//! error type each time. This module is essential for providing meaningful
14//! error messages to users of the semantic analysis library, helping them
15//! understand and fix issues in their VB6 code.
16//!
17//! # Examples
18//!
19//! ```rust
20//! use vb6semantic::SemanticError;
21//! use vb6semantic::SourceLocation;
22//!
23//! let error = SemanticError::UndefinedSymbol {
24//! name: "myVariable".to_string(),
25//! location: SourceLocation {
26//! file: "Module1.bas".to_string(),
27//! line: 10,
28//! column: 5,
29//! },
30//! };
31//! println!("{}", error);
32//! ```
33
34use serde::{Deserialize, Serialize};
35use thiserror::Error;
36use vb6parse::errors::ErrorDetails;
37
38/// Represents an error that can occur during semantic analysis of VB6 code
39///
40/// Each variant includes relevant information about the error, such as the symbol name,
41/// expected and found types, and source location. This allows for detailed error messages
42/// to help users understand and fix issues in their VB6 code.
43#[derive(Error, Debug, Clone)]
44pub enum SemanticError {
45 /// Represents an undefined symbol error, where a symbol is referenced but not defined
46 UndefinedSymbol {
47 /// Name of the undefined symbol
48 name: String,
49 /// Location where the undefined symbol is referenced
50 location: SourceLocation,
51 },
52
53 /// Represents a duplicate symbol error, where a symbol is defined multiple times
54 DuplicateSymbol {
55 /// Name of the duplicate symbol
56 name: String,
57 /// Location where the duplicate symbol is defined
58 location: SourceLocation,
59 /// Location where the symbol was previously defined
60 previous_location: SourceLocation,
61 },
62
63 /// Represents a type mismatch error, where the expected and found types do not match
64 TypeMismatch {
65 /// Expected type
66 expected: String,
67 /// Found type
68 found: String,
69 /// Location where the type mismatch occurs
70 location: SourceLocation,
71 },
72
73 /// Represents an invalid scope error, where a symbol is defined in an invalid scope
74 InvalidScope {
75 /// Message describing the invalid scope error
76 message: String,
77 },
78
79 /// Represents an invalid type error, where a type is not valid in the given context
80 InvalidType {
81 /// Message describing the invalid type error
82 message: String,
83 /// Location where the invalid type error occurs
84 location: SourceLocation,
85 },
86
87 /// Represents a circular dependency error, where symbols depend on each other in a cycle
88 CircularDependency {
89 /// Message describing the circular dependency error
90 message: String,
91 },
92
93 /// Represents an invalid operation error, where an operation is not valid for the given types
94 InvalidOperation {
95 /// Message describing the invalid operation error
96 message: String,
97 /// Location where the invalid operation occurs
98 location: SourceLocation,
99 },
100
101 /// Represents an inaccessible symbol error, where a symbol is not accessible due to its visibility
102 InaccessibleSymbol {
103 /// Name of the inaccessible symbol
104 name: String,
105 /// Visibility of the inaccessible symbol (Public, Private, Friend)
106 visibility: String,
107 /// Location where the inaccessible symbol is referenced
108 location: SourceLocation,
109 },
110
111 /// Represents an invalid assignment error, where an assignment is not valid due to type mismatch or other issues
112 InvalidAssignment {
113 /// Message describing the invalid assignment error
114 message: String,
115 /// Location where the invalid assignment occurs
116 location: SourceLocation,
117 },
118
119 /// Represents a parameter mismatch error, where the provided parameters do not match the expected ones
120 ParameterMismatch {
121 /// Message describing the parameter mismatch error
122 message: String,
123 /// Location where the parameter mismatch occurs
124 location: SourceLocation,
125 },
126
127 /// Represents a file read error, where a source file could not be read from disk
128 FileReadError {
129 /// Path of the file that could not be read
130 file: String,
131 /// Read error message
132 message: String,
133 },
134
135 /// Represents a parse error for a source file that could not be parsed successfully
136 FileParseError {
137 /// Path of the file that could not be parsed
138 file: String,
139 /// Underlying parser diagnostics
140 diagnostics: Vec<ErrorDetails<'static>>,
141 },
142
143 /// Represents a general analysis error that does not fit into other categories
144 AnalysisError(String),
145}
146
147impl std::fmt::Display for SemanticError {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 match self {
150 SemanticError::UndefinedSymbol { name, location } => {
151 write!(f, "Undefined symbol: {name} at {location}")
152 }
153 SemanticError::DuplicateSymbol {
154 name,
155 location,
156 previous_location,
157 } => write!(
158 f,
159 "Symbol already defined: {name} at {location}, previously defined at {previous_location}"
160 ),
161 SemanticError::TypeMismatch {
162 expected,
163 found,
164 location,
165 } => write!(
166 f,
167 "Type mismatch: expected {expected}, found {found} at {location}"
168 ),
169 SemanticError::InvalidScope { message } => write!(f, "Invalid scope: {message}"),
170 SemanticError::InvalidType { message, location } => {
171 write!(f, "Invalid type: {message} at {location}")
172 }
173 SemanticError::CircularDependency { message } => {
174 write!(f, "Circular dependency detected: {message}")
175 }
176 SemanticError::InvalidOperation { message, location } => {
177 write!(f, "Invalid operation: {message} at {location}")
178 }
179 SemanticError::InaccessibleSymbol {
180 name,
181 visibility,
182 location,
183 } => write!(
184 f,
185 "Inaccessible symbol: {name} is {visibility} at {location}"
186 ),
187 SemanticError::InvalidAssignment { message, location } => {
188 write!(f, "Invalid assignment: {message} at {location}")
189 }
190 SemanticError::ParameterMismatch { message, location } => {
191 write!(f, "Parameter mismatch: {message} at {location}")
192 }
193 SemanticError::FileReadError { file, message } => {
194 write!(f, "Failed to read file {file}: {message}")
195 }
196 SemanticError::FileParseError { file, diagnostics } => {
197 write!(f, "Failed to parse file {file}")?;
198 for diagnostic in diagnostics {
199 match diagnostic.print_to_string() {
200 Ok(text) => write!(f, "\n{text}")?,
201 Err(_) => write!(f, "\n{diagnostic:?}")?,
202 }
203 }
204 Ok(())
205 }
206 SemanticError::AnalysisError(message) => write!(f, "Analysis error: {message}"),
207 }
208 }
209}
210
211/// Represents a location in the source code, including file name, line number, and column number
212#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
213pub struct SourceLocation {
214 /// Name of the source file
215 pub file: String,
216 /// Line number in the source file (1-based)
217 pub line: usize,
218 /// Column number in the source file (1-based)
219 pub column: usize,
220}
221
222impl std::fmt::Display for SourceLocation {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 write!(f, "{}:{}:{}", self.file, self.line, self.column)
225 }
226}
227
228/// Type alias for results returned by semantic analysis functions, using `SemanticError` as the error type
229pub type Result<T> = std::result::Result<T, SemanticError>;
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn file_read_error_formats_file_and_source() {
237 let error = SemanticError::FileReadError {
238 file: "module.bas".to_string(),
239 message: "permission denied".to_string(),
240 };
241
242 let message = error.to_string();
243 assert!(message.contains("module.bas"));
244 assert!(message.contains("permission denied"));
245 }
246
247 #[test]
248 fn file_parse_error_pretty_prints_source_diagnostics() {
249 let error = SemanticError::FileParseError {
250 file: "module.bas".to_string(),
251 diagnostics: vec![ErrorDetails {
252 source_name: "module.bas".to_string().into_boxed_str(),
253 source_content: "Dim x As ?",
254 error_offset: 8,
255 line_start: 1,
256 line_end: 1,
257 kind: Box::new(vb6parse::errors::ErrorKind::Lexer(
258 vb6parse::errors::LexerError::UnknownToken {
259 token: "?".to_string(),
260 },
261 )),
262 severity: vb6parse::errors::Severity::Error,
263 labels: vec![],
264 notes: vec![],
265 }],
266 };
267
268 let message = error.to_string();
269 assert!(message.contains("module.bas"));
270 assert!(message.contains("error here"));
271 assert!(
272 !message.contains("ErrorDetails {"),
273 "diagnostics should be rendered with the source display, not Debug"
274 );
275 }
276}