neo_solidity/
error.rs

1//! Compiler Error Types
2//!
3//! Defines error types used throughout the Neo Solidity compiler pipeline.
4//! All errors implement the standard `Error` trait for easy integration.
5//!
6//! # Error Categories
7//!
8//! - [`CompilerError::IoError`] - File system and I/O errors
9//! - [`CompilerError::ParseError`] - Syntax and parsing errors
10//! - [`CompilerError::SemanticError`] - Type checking and semantic errors
11//! - [`CompilerError::CodegenError`] - Code generation errors
12//!
13//! # Source Location
14//!
15//! Errors can include source location information via [`SourceLocation`] for
16//! better diagnostic messages.
17//!
18//! # Fix Suggestions
19//!
20//! Errors can include [`FixSuggestion`] to help users resolve issues.
21
22use thiserror::Error;
23
24/// Source location information for error reporting
25#[derive(Debug, Clone, Default, PartialEq, Eq)]
26pub struct SourceLocation {
27    /// Source file path (if available)
28    pub file: Option<String>,
29    /// Line number (1-indexed)
30    pub line: usize,
31    /// Column number (1-indexed)
32    pub column: usize,
33    /// Length of the error span
34    pub length: usize,
35    /// End line for multi-line spans
36    pub end_line: Option<usize>,
37    /// End column for multi-line spans
38    pub end_column: Option<usize>,
39}
40
41impl SourceLocation {
42    /// Create a new source location
43    pub fn new(line: usize, column: usize) -> Self {
44        Self {
45            file: None,
46            line,
47            column,
48            length: 0,
49            end_line: None,
50            end_column: None,
51        }
52    }
53
54    /// Create a source location with file information
55    pub fn with_file(file: impl Into<String>, line: usize, column: usize) -> Self {
56        Self {
57            file: Some(file.into()),
58            line,
59            column,
60            length: 0,
61            end_line: None,
62            end_column: None,
63        }
64    }
65
66    /// Set the span length
67    pub fn with_length(mut self, length: usize) -> Self {
68        self.length = length;
69        self
70    }
71
72    /// Set the end position for multi-line spans
73    pub fn with_end(mut self, end_line: usize, end_column: usize) -> Self {
74        self.end_line = Some(end_line);
75        self.end_column = Some(end_column);
76        self
77    }
78
79    /// Check if this is a valid location
80    pub fn is_valid(&self) -> bool {
81        self.line > 0 && self.column > 0
82    }
83
84    /// Create a span from start to end location
85    pub fn span_to(&self, end: &SourceLocation) -> Self {
86        Self {
87            file: self.file.clone(),
88            line: self.line,
89            column: self.column,
90            length: 0,
91            end_line: Some(end.line),
92            end_column: Some(end.column),
93        }
94    }
95}
96
97impl std::fmt::Display for SourceLocation {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        match &self.file {
100            Some(file) => write!(f, "{}:{}:{}", file, self.line, self.column),
101            None => write!(f, "{}:{}", self.line, self.column),
102        }
103    }
104}
105
106/// A suggested fix for an error
107#[derive(Debug, Clone)]
108pub struct FixSuggestion {
109    /// Description of the fix
110    pub message: String,
111    /// The replacement text (if applicable)
112    pub replacement: Option<String>,
113    /// Location where the fix should be applied
114    pub location: Option<SourceLocation>,
115    /// Whether this fix can be applied automatically
116    pub is_auto_fixable: bool,
117}
118
119impl FixSuggestion {
120    /// Create a simple suggestion message
121    pub fn hint(message: impl Into<String>) -> Self {
122        Self {
123            message: message.into(),
124            replacement: None,
125            location: None,
126            is_auto_fixable: false,
127        }
128    }
129
130    /// Create a suggestion with replacement text
131    pub fn replace(message: impl Into<String>, replacement: impl Into<String>) -> Self {
132        Self {
133            message: message.into(),
134            replacement: Some(replacement.into()),
135            location: None,
136            is_auto_fixable: true,
137        }
138    }
139
140    /// Create a suggestion with location
141    pub fn at(mut self, location: SourceLocation) -> Self {
142        self.location = Some(location);
143        self
144    }
145}
146
147impl std::fmt::Display for FixSuggestion {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        write!(f, "help: {}", self.message)?;
150        if let Some(ref replacement) = self.replacement {
151            write!(f, "\n  suggestion: `{}`", replacement)?;
152        }
153        Ok(())
154    }
155}
156
157/// Related diagnostic information
158#[derive(Debug, Clone)]
159pub struct RelatedInfo {
160    /// Location of related code
161    pub location: SourceLocation,
162    /// Description of the relation
163    pub message: String,
164}
165
166impl RelatedInfo {
167    pub fn new(location: SourceLocation, message: impl Into<String>) -> Self {
168        Self {
169            location,
170            message: message.into(),
171        }
172    }
173}
174
175/// Diagnostic data (boxed to reduce CompilerError size)
176#[derive(Debug, Clone)]
177pub struct DiagnosticData {
178    pub severity: ErrorSeverity,
179    pub location: SourceLocation,
180    pub message: String,
181    pub code: ErrorCode,
182    pub suggestions: Vec<FixSuggestion>,
183    pub related: Vec<RelatedInfo>,
184    pub source_snippet: Option<String>,
185}
186
187impl std::fmt::Display for DiagnosticData {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        write!(
190            f,
191            "{}[{}] at {}: {}",
192            self.severity, self.code, self.location, self.message
193        )
194    }
195}
196
197#[derive(Error, Debug)]
198pub enum CompilerError {
199    #[error("IO error: {0}")]
200    IoError(String),
201
202    #[error("Parse error: {0}")]
203    ParseError(String),
204
205    #[error("Semantic error: {0}")]
206    SemanticError(String),
207
208    #[error("Codegen error: {0}")]
209    CodegenError(String),
210
211    #[error("{severity} at {location}: {message}")]
212    Located {
213        severity: ErrorSeverity,
214        location: SourceLocation,
215        message: String,
216        code: Option<ErrorCode>,
217    },
218
219    #[error("{0}")]
220    Diagnostic(Box<DiagnosticData>),
221}
222
223/// Error severity levels
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum ErrorSeverity {
226    Error,
227    Warning,
228    Info,
229    Hint,
230}
231
232impl std::fmt::Display for ErrorSeverity {
233    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234        match self {
235            Self::Error => write!(f, "error"),
236            Self::Warning => write!(f, "warning"),
237            Self::Info => write!(f, "info"),
238            Self::Hint => write!(f, "hint"),
239        }
240    }
241}
242
243/// Error codes for programmatic error handling
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
245pub enum ErrorCode {
246    // Parse errors (1xxx)
247    UnexpectedToken = 1001,
248    UnexpectedEof = 1002,
249    InvalidSyntax = 1003,
250    MissingSemicolon = 1004,
251    MissingBrace = 1005,
252    InvalidNumber = 1006,
253    InvalidString = 1007,
254    InvalidIdentifier = 1008,
255
256    // Semantic errors (2xxx)
257    UndefinedVariable = 2001,
258    TypeMismatch = 2002,
259    DuplicateDefinition = 2003,
260    UndefinedFunction = 2004,
261    UndefinedType = 2005,
262    InvalidAssignment = 2006,
263    ImmutableModification = 2007,
264    VisibilityError = 2008,
265    InvalidOverride = 2009,
266    MissingReturn = 2010,
267    UnreachableCode = 2011,
268    UnusedVariable = 2012,
269    UnusedFunction = 2013,
270    ShadowedVariable = 2014,
271    InvalidModifier = 2015,
272
273    // Type errors (25xx)
274    IntegerOverflow = 2501,
275    IntegerUnderflow = 2502,
276    DivisionByZero = 2503,
277    ArrayOutOfBounds = 2504,
278    InvalidCast = 2505,
279    NullReference = 2506,
280
281    // Codegen errors (3xxx)
282    UnsupportedFeature = 3001,
283    InvalidBytecode = 3002,
284    StackOverflow = 3003,
285    GasLimitExceeded = 3004,
286    ContractTooLarge = 3005,
287    InvalidOpcode = 3006,
288    BreakOutsideLoop = 3007,
289    ContinueOutsideLoop = 3008,
290    InvalidJumpOffset = 3009,
291
292    // IO errors (4xxx)
293    FileNotFound = 4001,
294    PermissionDenied = 4002,
295    ImportNotFound = 4003,
296    CircularImport = 4004,
297
298    // Security warnings (5xxx)
299    ReentrancyRisk = 5001,
300    UncheckedCall = 5002,
301    TxOriginUsage = 5003,
302    UnsafeDelegate = 5004,
303    IntegerOverflowRisk = 5005,
304}
305
306impl std::fmt::Display for ErrorCode {
307    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308        write!(f, "E{:04}", *self as u32)
309    }
310}
311
312impl ErrorCode {
313    /// Get a human-readable description of the error code
314    pub fn description(&self) -> &'static str {
315        match self {
316            Self::UnexpectedToken => "unexpected token in input",
317            Self::UnexpectedEof => "unexpected end of file",
318            Self::InvalidSyntax => "invalid syntax",
319            Self::MissingSemicolon => "missing semicolon",
320            Self::MissingBrace => "missing brace",
321            Self::InvalidNumber => "invalid number literal",
322            Self::InvalidString => "invalid string literal",
323            Self::InvalidIdentifier => "invalid identifier",
324            Self::UndefinedVariable => "undefined variable",
325            Self::TypeMismatch => "type mismatch",
326            Self::DuplicateDefinition => "duplicate definition",
327            Self::UndefinedFunction => "undefined function",
328            Self::UndefinedType => "undefined type",
329            Self::InvalidAssignment => "invalid assignment",
330            Self::ImmutableModification => "cannot modify immutable value",
331            Self::VisibilityError => "visibility error",
332            Self::InvalidOverride => "invalid override",
333            Self::MissingReturn => "missing return statement",
334            Self::UnreachableCode => "unreachable code",
335            Self::UnusedVariable => "unused variable",
336            Self::UnusedFunction => "unused function",
337            Self::ShadowedVariable => "variable shadows outer scope",
338            Self::InvalidModifier => "invalid modifier",
339            Self::IntegerOverflow => "integer overflow",
340            Self::IntegerUnderflow => "integer underflow",
341            Self::DivisionByZero => "division by zero",
342            Self::ArrayOutOfBounds => "array index out of bounds",
343            Self::InvalidCast => "invalid type cast",
344            Self::NullReference => "null reference",
345            Self::UnsupportedFeature => "unsupported feature",
346            Self::InvalidBytecode => "invalid bytecode",
347            Self::StackOverflow => "stack overflow",
348            Self::GasLimitExceeded => "gas limit exceeded",
349            Self::ContractTooLarge => "contract too large",
350            Self::InvalidOpcode => "invalid opcode",
351            Self::BreakOutsideLoop => "break statement outside of loop",
352            Self::ContinueOutsideLoop => "continue statement outside of loop",
353            Self::InvalidJumpOffset => "invalid jump offset in bytecode",
354            Self::FileNotFound => "file not found",
355            Self::PermissionDenied => "permission denied",
356            Self::ImportNotFound => "import not found",
357            Self::CircularImport => "circular import detected",
358            Self::ReentrancyRisk => "potential reentrancy vulnerability",
359            Self::UncheckedCall => "unchecked external call",
360            Self::TxOriginUsage => "tx.origin used for authorization",
361            Self::UnsafeDelegate => "unsafe delegatecall",
362            Self::IntegerOverflowRisk => "potential integer overflow",
363        }
364    }
365}
366
367impl CompilerError {
368    /// Create a located error with full context
369    pub fn located(
370        severity: ErrorSeverity,
371        location: SourceLocation,
372        message: impl Into<String>,
373        code: Option<ErrorCode>,
374    ) -> Self {
375        Self::Located {
376            severity,
377            location,
378            message: message.into(),
379            code,
380        }
381    }
382
383    /// Create a parse error at a specific location
384    pub fn parse_at(location: SourceLocation, message: impl Into<String>) -> Self {
385        Self::located(
386            ErrorSeverity::Error,
387            location,
388            message,
389            Some(ErrorCode::InvalidSyntax),
390        )
391    }
392
393    /// Create a semantic error at a specific location with the given error code.
394    pub fn semantic_at(
395        location: SourceLocation,
396        message: impl Into<String>,
397        code: ErrorCode,
398    ) -> Self {
399        Self::located(ErrorSeverity::Error, location, message, Some(code))
400    }
401
402    /// Check if this is a fatal error
403    pub fn is_fatal(&self) -> bool {
404        match self {
405            Self::Located { severity, .. } => *severity == ErrorSeverity::Error,
406            Self::Diagnostic(d) => d.severity == ErrorSeverity::Error,
407            _ => true,
408        }
409    }
410
411    /// Check if this is a warning
412    pub fn is_warning(&self) -> bool {
413        match self {
414            Self::Located { severity, .. } => *severity == ErrorSeverity::Warning,
415            Self::Diagnostic(d) => d.severity == ErrorSeverity::Warning,
416            _ => false,
417        }
418    }
419
420    /// Get the error code if available
421    pub fn code(&self) -> Option<ErrorCode> {
422        match self {
423            Self::Located { code, .. } => *code,
424            Self::Diagnostic(d) => Some(d.code),
425            _ => None,
426        }
427    }
428
429    /// Get suggestions if available
430    pub fn suggestions(&self) -> &[FixSuggestion] {
431        match self {
432            Self::Diagnostic(d) => &d.suggestions,
433            _ => &[],
434        }
435    }
436}
437
438impl From<std::io::Error> for CompilerError {
439    fn from(err: std::io::Error) -> Self {
440        Self::IoError(err.to_string())
441    }
442}
443
444/// Builder for creating rich diagnostic messages
445#[derive(Debug)]
446pub struct DiagnosticBuilder {
447    severity: ErrorSeverity,
448    location: SourceLocation,
449    message: String,
450    code: ErrorCode,
451    suggestions: Vec<FixSuggestion>,
452    related: Vec<RelatedInfo>,
453    source_snippet: Option<String>,
454}
455
456impl DiagnosticBuilder {
457    /// Create a new error diagnostic
458    pub fn error(code: ErrorCode, message: impl Into<String>) -> Self {
459        Self {
460            severity: ErrorSeverity::Error,
461            location: SourceLocation::default(),
462            message: message.into(),
463            code,
464            suggestions: Vec::new(),
465            related: Vec::new(),
466            source_snippet: None,
467        }
468    }
469
470    /// Create a new warning diagnostic
471    pub fn warning(code: ErrorCode, message: impl Into<String>) -> Self {
472        Self {
473            severity: ErrorSeverity::Warning,
474            location: SourceLocation::default(),
475            message: message.into(),
476            code,
477            suggestions: Vec::new(),
478            related: Vec::new(),
479            source_snippet: None,
480        }
481    }
482
483    /// Set the source location
484    pub fn at(mut self, location: SourceLocation) -> Self {
485        self.location = location;
486        self
487    }
488
489    /// Add a fix suggestion
490    pub fn suggest(mut self, suggestion: FixSuggestion) -> Self {
491        self.suggestions.push(suggestion);
492        self
493    }
494
495    /// Add related information
496    pub fn related(mut self, info: RelatedInfo) -> Self {
497        self.related.push(info);
498        self
499    }
500
501    /// Add source code snippet
502    pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
503        self.source_snippet = Some(snippet.into());
504        self
505    }
506
507    /// Build the final CompilerError
508    pub fn build(self) -> CompilerError {
509        CompilerError::Diagnostic(Box::new(DiagnosticData {
510            severity: self.severity,
511            location: self.location,
512            message: self.message,
513            code: self.code,
514            suggestions: self.suggestions,
515            related: self.related,
516            source_snippet: self.source_snippet,
517        }))
518    }
519}