Skip to main content

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}