1use std::fmt;
2use thiserror::Error;
3
4#[derive(Debug, Clone, PartialEq)]
6pub struct SourceLocation {
7 pub file: String,
8 pub line: usize,
9 pub column: usize,
10}
11
12impl fmt::Display for SourceLocation {
13 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14 write!(f, "{}:{}:{}", self.file, self.line, self.column)
15 }
16}
17
18#[derive(Debug, Error)]
20pub enum ErrorKind {
21 #[error("Python parse error")]
22 ParseError,
23
24 #[error("Unsupported Python feature")]
25 UnsupportedFeature(String),
26
27 #[error("Type inference error")]
28 TypeInferenceError(String),
29
30 #[error("Invalid type annotation")]
31 InvalidTypeAnnotation(String),
32
33 #[error("Type mismatch")]
34 TypeMismatch {
35 expected: String,
36 found: String,
37 context: String,
38 },
39
40 #[error("Code generation error")]
41 CodeGenerationError(String),
42
43 #[error("Verification failed")]
44 VerificationError(String),
45
46 #[error("Internal error")]
47 InternalError(String),
48}
49
50#[derive(Debug, Error)]
52pub struct TranspileError {
53 pub kind: ErrorKind,
54 pub location: Option<SourceLocation>,
55 pub context: Vec<String>,
56 pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
57}
58
59impl TranspileError {
60 pub fn new(kind: ErrorKind) -> Self {
62 Self {
63 kind,
64 location: None,
65 context: Vec::new(),
66 source: None,
67 }
68 }
69
70 pub fn with_location(mut self, location: SourceLocation) -> Self {
72 self.location = Some(location);
73 self
74 }
75
76 pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
78 self.context.push(ctx.into());
79 self
80 }
81
82 pub fn with_source(mut self, source: impl std::error::Error + Send + Sync + 'static) -> Self {
84 self.source = Some(Box::new(source));
85 self
86 }
87}
88
89impl fmt::Display for TranspileError {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 write!(f, "{}", self.kind)?;
93
94 self.format_location(f)?;
96
97 self.format_context_list(f)?;
99
100 Ok(())
101 }
102}
103
104impl TranspileError {
105 #[inline]
107 fn format_location(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 if let Some(loc) = &self.location {
109 write!(f, " at {loc}")?;
110 }
111 Ok(())
112 }
113
114 #[inline]
116 fn format_context_list(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 if !self.context.is_empty() {
118 write!(f, "\n\nContext:")?;
119 for (i, ctx) in self.context.iter().enumerate() {
120 write!(f, "\n {}. {}", i + 1, ctx)?;
121 }
122 }
123 Ok(())
124 }
125}
126
127impl TranspileError {
128 pub fn backend_error(msg: impl Into<String>) -> Self {
130 Self::new(ErrorKind::CodeGenerationError(msg.into()))
131 }
132
133 pub fn transform_error(msg: impl Into<String>) -> Self {
135 Self::new(ErrorKind::CodeGenerationError(format!(
136 "Transformation failed: {}",
137 msg.into()
138 )))
139 }
140
141 pub fn optimization_error(msg: impl Into<String>) -> Self {
143 Self::new(ErrorKind::InternalError(format!(
144 "Optimization failed: {}",
145 msg.into()
146 )))
147 }
148}
149
150pub type TranspileResult<T> = Result<T, TranspileError>;
152
153pub trait ResultExt<T> {
155 #[allow(clippy::result_large_err)]
156 fn with_context(self, ctx: impl Into<String>) -> TranspileResult<T>;
157}
158
159impl<T, E> ResultExt<T> for Result<T, E>
160where
161 E: Into<TranspileError>,
162{
163 fn with_context(self, ctx: impl Into<String>) -> TranspileResult<T> {
164 self.map_err(|e| e.into().with_context(ctx))
165 }
166}
167
168impl From<anyhow::Error> for TranspileError {
170 fn from(err: anyhow::Error) -> Self {
171 TranspileError::new(ErrorKind::InternalError(err.to_string()))
172 }
173}
174
175#[macro_export]
177macro_rules! transpile_error {
178 ($kind:expr) => {
179 $crate::error::TranspileError::new($kind)
180 };
181
182 ($kind:expr, $($ctx:expr),+) => {{
183 let mut err = $crate::error::TranspileError::new($kind);
184 $(
185 err = err.with_context($ctx);
186 )+
187 err
188 }};
189}
190
191#[macro_export]
193macro_rules! transpile_bail {
194 ($kind:expr) => {
195 return Err($crate::transpile_error!($kind))
196 };
197
198 ($kind:expr, $($ctx:expr),+) => {
199 return Err($crate::transpile_error!($kind, $($ctx),+))
200 };
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
210 fn test_source_location_new() {
211 let loc = SourceLocation {
212 file: "test.py".to_string(),
213 line: 10,
214 column: 5,
215 };
216 assert_eq!(loc.file, "test.py");
217 assert_eq!(loc.line, 10);
218 assert_eq!(loc.column, 5);
219 }
220
221 #[test]
222 fn test_source_location_display() {
223 let loc = SourceLocation {
224 file: "example.py".to_string(),
225 line: 42,
226 column: 8,
227 };
228 assert_eq!(format!("{}", loc), "example.py:42:8");
229 }
230
231 #[test]
232 fn test_source_location_display_edge_cases() {
233 let loc = SourceLocation {
234 file: "".to_string(),
235 line: 0,
236 column: 0,
237 };
238 assert_eq!(format!("{}", loc), ":0:0");
239 }
240
241 #[test]
242 fn test_source_location_clone() {
243 let loc = SourceLocation {
244 file: "test.py".to_string(),
245 line: 1,
246 column: 1,
247 };
248 let cloned = loc.clone();
249 assert_eq!(loc, cloned);
250 }
251
252 #[test]
253 fn test_source_location_partial_eq() {
254 let loc1 = SourceLocation {
255 file: "a.py".to_string(),
256 line: 1,
257 column: 1,
258 };
259 let loc2 = SourceLocation {
260 file: "a.py".to_string(),
261 line: 1,
262 column: 1,
263 };
264 let loc3 = SourceLocation {
265 file: "b.py".to_string(),
266 line: 1,
267 column: 1,
268 };
269 assert_eq!(loc1, loc2);
270 assert_ne!(loc1, loc3);
271 }
272
273 #[test]
274 fn test_source_location_debug() {
275 let loc = SourceLocation {
276 file: "test.py".to_string(),
277 line: 5,
278 column: 10,
279 };
280 let debug = format!("{:?}", loc);
281 assert!(debug.contains("SourceLocation"));
282 assert!(debug.contains("test.py"));
283 }
284
285 #[test]
288 fn test_error_kind_parse_error() {
289 let err = ErrorKind::ParseError;
290 assert_eq!(format!("{}", err), "Python parse error");
291 }
292
293 #[test]
294 fn test_error_kind_unsupported_feature() {
295 let err = ErrorKind::UnsupportedFeature("async generators".to_string());
296 let display = format!("{}", err);
297 assert!(display.contains("Unsupported Python feature"));
298 }
299
300 #[test]
301 fn test_error_kind_type_inference_error() {
302 let err = ErrorKind::TypeInferenceError("cannot infer type".to_string());
303 let display = format!("{}", err);
304 assert!(display.contains("Type inference error"));
305 }
306
307 #[test]
308 fn test_error_kind_invalid_type_annotation() {
309 let err = ErrorKind::InvalidTypeAnnotation("List[Unknown]".to_string());
310 let display = format!("{}", err);
311 assert!(display.contains("Invalid type annotation"));
312 }
313
314 #[test]
315 fn test_error_kind_type_mismatch() {
316 let err = ErrorKind::TypeMismatch {
317 expected: "int".to_string(),
318 found: "str".to_string(),
319 context: "function return".to_string(),
320 };
321 let display = format!("{}", err);
322 assert!(display.contains("Type mismatch"));
323 }
324
325 #[test]
326 fn test_error_kind_code_generation_error() {
327 let err = ErrorKind::CodeGenerationError("failed to generate".to_string());
328 let display = format!("{}", err);
329 assert!(display.contains("Code generation error"));
330 }
331
332 #[test]
333 fn test_error_kind_verification_error() {
334 let err = ErrorKind::VerificationError("verification failed".to_string());
335 let display = format!("{}", err);
336 assert!(display.contains("Verification failed"));
337 }
338
339 #[test]
340 fn test_error_kind_internal_error() {
341 let err = ErrorKind::InternalError("unexpected state".to_string());
342 let display = format!("{}", err);
343 assert!(display.contains("Internal error"));
344 }
345
346 #[test]
347 fn test_error_kind_debug() {
348 let err = ErrorKind::ParseError;
349 let debug = format!("{:?}", err);
350 assert!(debug.contains("ParseError"));
351 }
352
353 #[test]
356 fn test_error_creation() {
357 let err = TranspileError::new(ErrorKind::UnsupportedFeature("async/await".to_string()));
358 assert!(matches!(err.kind, ErrorKind::UnsupportedFeature(_)));
359 assert!(err.location.is_none());
360 assert!(err.context.is_empty());
361 }
362
363 #[test]
364 fn test_error_with_location() {
365 let loc = SourceLocation {
366 file: "test.py".to_string(),
367 line: 10,
368 column: 5,
369 };
370
371 let err = TranspileError::new(ErrorKind::ParseError).with_location(loc.clone());
372
373 assert_eq!(err.location.unwrap(), loc);
374 }
375
376 #[test]
377 fn test_error_with_context() {
378 let err = TranspileError::new(ErrorKind::TypeInferenceError("unknown type".to_string()))
379 .with_context("in function 'add'")
380 .with_context("while processing parameter 'x'");
381
382 assert_eq!(err.context.len(), 2);
383 assert_eq!(err.context[0], "in function 'add'");
384 assert_eq!(err.context[1], "while processing parameter 'x'");
385 }
386
387 #[test]
388 fn test_error_with_source() {
389 let source_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
390 let err = TranspileError::new(ErrorKind::InternalError("io error".to_string()))
391 .with_source(source_err);
392 assert!(err.source.is_some());
393 }
394
395 #[test]
396 fn test_error_display() {
397 let loc = SourceLocation {
398 file: "example.py".to_string(),
399 line: 25,
400 column: 10,
401 };
402
403 let err = TranspileError::new(ErrorKind::UnsupportedFeature("decorators".to_string()))
404 .with_location(loc)
405 .with_context("in function 'my_func'")
406 .with_context("processing @decorator syntax");
407
408 let display = format!("{err}");
409 assert!(display.contains("Unsupported Python feature"));
410 assert!(display.contains("example.py:25:10"));
411 assert!(display.contains("in function 'my_func'"));
412 }
413
414 #[test]
415 fn test_error_display_no_location() {
416 let err = TranspileError::new(ErrorKind::ParseError);
417 let display = format!("{}", err);
418 assert!(display.contains("Python parse error"));
419 assert!(!display.contains("at "));
420 }
421
422 #[test]
423 fn test_error_display_no_context() {
424 let err = TranspileError::new(ErrorKind::ParseError);
425 let display = format!("{}", err);
426 assert!(!display.contains("Context:"));
427 }
428
429 #[test]
430 fn test_error_display_with_numbered_context() {
431 let err = TranspileError::new(ErrorKind::ParseError)
432 .with_context("first")
433 .with_context("second")
434 .with_context("third");
435 let display = format!("{}", err);
436 assert!(display.contains("1. first"));
437 assert!(display.contains("2. second"));
438 assert!(display.contains("3. third"));
439 }
440
441 #[test]
442 fn test_error_builder_chain() {
443 let loc = SourceLocation {
444 file: "chain.py".to_string(),
445 line: 1,
446 column: 1,
447 };
448 let source_err = std::io::Error::other("test");
449
450 let err = TranspileError::new(ErrorKind::InternalError("test".to_string()))
451 .with_location(loc.clone())
452 .with_context("ctx1")
453 .with_source(source_err);
454
455 assert_eq!(err.location, Some(loc));
456 assert_eq!(err.context.len(), 1);
457 assert!(err.source.is_some());
458 }
459
460 #[test]
461 fn test_error_debug() {
462 let err = TranspileError::new(ErrorKind::ParseError);
463 let debug = format!("{:?}", err);
464 assert!(debug.contains("TranspileError"));
465 assert!(debug.contains("ParseError"));
466 }
467
468 #[test]
471 fn test_result_ext_ok() {
472 let result: Result<i32, TranspileError> = Ok(42);
473 let with_ctx = result.with_context("extra context");
474 assert_eq!(with_ctx.unwrap(), 42);
475 }
476
477 #[test]
478 fn test_result_ext_err() {
479 let result: Result<i32, TranspileError> = Err(TranspileError::new(ErrorKind::ParseError));
480 let with_ctx = result.with_context("added context");
481 let err = with_ctx.unwrap_err();
482 assert_eq!(err.context.len(), 1);
483 assert_eq!(err.context[0], "added context");
484 }
485
486 #[test]
489 fn test_from_anyhow_error() {
490 let anyhow_err = anyhow::anyhow!("something went wrong");
491 let err: TranspileError = anyhow_err.into();
492 assert!(matches!(err.kind, ErrorKind::InternalError(_)));
493 let display = format!("{}", err);
494 assert!(display.contains("Internal error"));
495 }
496
497 #[test]
500 fn test_transpile_error_macro() {
501 let err1 = transpile_error!(ErrorKind::ParseError);
502 assert!(matches!(err1.kind, ErrorKind::ParseError));
503
504 let err2 = transpile_error!(
505 ErrorKind::TypeInferenceError("test".to_string()),
506 "context 1",
507 "context 2"
508 );
509 assert_eq!(err2.context.len(), 2);
510 }
511
512 #[test]
513 fn test_transpile_error_macro_single_context() {
514 let err = transpile_error!(ErrorKind::ParseError, "single context");
515 assert_eq!(err.context.len(), 1);
516 assert_eq!(err.context[0], "single context");
517 }
518
519 #[test]
520 fn test_transpile_error_macro_many_contexts() {
521 let err = transpile_error!(
522 ErrorKind::InternalError("test".to_string()),
523 "ctx1",
524 "ctx2",
525 "ctx3",
526 "ctx4"
527 );
528 assert_eq!(err.context.len(), 4);
529 }
530
531 #[allow(clippy::result_large_err)]
532 fn bail_test_helper() -> TranspileResult<()> {
533 transpile_bail!(ErrorKind::ParseError);
534 }
535
536 #[test]
537 fn test_transpile_bail_macro() {
538 let result = bail_test_helper();
539 assert!(result.is_err());
540 let err = result.unwrap_err();
541 assert!(matches!(err.kind, ErrorKind::ParseError));
542 }
543
544 #[allow(clippy::result_large_err)]
545 fn bail_test_with_context() -> TranspileResult<()> {
546 transpile_bail!(ErrorKind::ParseError, "context1", "context2");
547 }
548
549 #[test]
550 fn test_transpile_bail_macro_with_context() {
551 let result = bail_test_with_context();
552 assert!(result.is_err());
553 let err = result.unwrap_err();
554 assert_eq!(err.context.len(), 2);
555 }
556
557 #[test]
560 fn test_transpile_result_ok() {
561 let result: TranspileResult<i32> = Ok(42);
562 assert!(result.is_ok());
563 }
564
565 #[test]
566 fn test_transpile_result_err() {
567 let result: TranspileResult<i32> = Err(TranspileError::new(ErrorKind::ParseError));
568 assert!(result.is_err());
569 }
570
571 #[test]
574 fn test_empty_strings_in_error_kind() {
575 let err = ErrorKind::UnsupportedFeature("".to_string());
576 let display = format!("{}", err);
577 assert!(display.contains("Unsupported Python feature"));
578 }
579
580 #[test]
581 fn test_type_mismatch_all_empty() {
582 let err = ErrorKind::TypeMismatch {
583 expected: "".to_string(),
584 found: "".to_string(),
585 context: "".to_string(),
586 };
587 let display = format!("{}", err);
588 assert!(display.contains("Type mismatch"));
589 }
590
591 #[test]
592 fn test_context_with_special_chars() {
593 let err = TranspileError::new(ErrorKind::ParseError)
594 .with_context("context with 'quotes' and \"double quotes\"")
595 .with_context("context\nwith\nnewlines");
596 assert_eq!(err.context.len(), 2);
597 }
598
599 #[test]
600 fn test_long_context_chain() {
601 let mut err = TranspileError::new(ErrorKind::ParseError);
602 for i in 0..100 {
603 err = err.with_context(format!("context {}", i));
604 }
605 assert_eq!(err.context.len(), 100);
606 }
607}