1use thiserror::Error;
23
24#[derive(Debug, Clone, Default, PartialEq, Eq)]
26pub struct SourceLocation {
27 pub file: Option<String>,
29 pub line: usize,
31 pub column: usize,
33 pub length: usize,
35 pub end_line: Option<usize>,
37 pub end_column: Option<usize>,
39}
40
41impl SourceLocation {
42 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 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 pub fn with_length(mut self, length: usize) -> Self {
68 self.length = length;
69 self
70 }
71
72 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 pub fn is_valid(&self) -> bool {
81 self.line > 0 && self.column > 0
82 }
83
84 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#[derive(Debug, Clone)]
108pub struct FixSuggestion {
109 pub message: String,
111 pub replacement: Option<String>,
113 pub location: Option<SourceLocation>,
115 pub is_auto_fixable: bool,
117}
118
119impl FixSuggestion {
120 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 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 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#[derive(Debug, Clone)]
159pub struct RelatedInfo {
160 pub location: SourceLocation,
162 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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
245pub enum ErrorCode {
246 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 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 IntegerOverflow = 2501,
275 IntegerUnderflow = 2502,
276 DivisionByZero = 2503,
277 ArrayOutOfBounds = 2504,
278 InvalidCast = 2505,
279 NullReference = 2506,
280
281 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 FileNotFound = 4001,
294 PermissionDenied = 4002,
295 ImportNotFound = 4003,
296 CircularImport = 4004,
297
298 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 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 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 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 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 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 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 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 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#[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 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 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 pub fn at(mut self, location: SourceLocation) -> Self {
485 self.location = location;
486 self
487 }
488
489 pub fn suggest(mut self, suggestion: FixSuggestion) -> Self {
491 self.suggestions.push(suggestion);
492 self
493 }
494
495 pub fn related(mut self, info: RelatedInfo) -> Self {
497 self.related.push(info);
498 self
499 }
500
501 pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
503 self.source_snippet = Some(snippet.into());
504 self
505 }
506
507 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}