use crate::error::ErrorKind;
use anyhow::Result;
use colored::Colorize;
use rustpython_ast::Ranged;
use std::fmt;
#[derive(Debug)]
pub struct EnhancedError {
pub error: ErrorKind,
pub file_path: Option<String>,
pub line: Option<usize>,
pub column: Option<usize>,
pub source_line: Option<String>,
pub suggestion: Option<String>,
pub notes: Vec<String>,
}
impl EnhancedError {
pub fn new(error: ErrorKind) -> Self {
Self {
error,
file_path: None,
line: None,
column: None,
source_line: None,
suggestion: None,
notes: Vec::new(),
}
}
pub fn with_location(mut self, file: &str, line: usize, column: usize) -> Self {
self.file_path = Some(file.to_string());
self.line = Some(line);
self.column = Some(column);
self
}
pub fn with_source_line(mut self, line: &str) -> Self {
self.source_line = Some(line.to_string());
self
}
pub fn with_suggestion(mut self, suggestion: &str) -> Self {
self.suggestion = Some(suggestion.to_string());
self
}
pub fn add_note(mut self, note: &str) -> Self {
self.notes.push(note.to_string());
self
}
pub fn from_ast_node<T: Ranged>(error: ErrorKind, node: &T, source: &str) -> Self {
let range = node.range();
let (line, column) = get_line_column(source, range.start().into());
let mut enhanced = Self::new(error).with_location("<input>", line, column);
if let Some(line_text) = get_source_line(source, line) {
enhanced = enhanced.with_source_line(&line_text);
}
enhanced = add_automatic_suggestions(enhanced);
enhanced
}
}
impl fmt::Display for EnhancedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{}: {}", "error".red().bold(), self.error)?;
if let (Some(file), Some(line), Some(column)) = (&self.file_path, self.line, self.column) {
writeln!(f, " {} {}:{}:{}", "-->".blue().bold(), file, line, column)?;
}
if let (Some(line_text), Some(line_num), Some(col)) =
(&self.source_line, self.line, self.column)
{
writeln!(f, " {} |", format!("{:4}", " ").dimmed())?;
writeln!(
f,
" {} | {}",
format!("{:4}", line_num).blue().bold(),
line_text
)?;
writeln!(
f,
" {} | {}{}",
format!("{:4}", " ").dimmed(),
" ".repeat(col.saturating_sub(1)),
"^".red().bold()
)?;
}
if let Some(suggestion) = &self.suggestion {
writeln!(f, "\n{}: {}", "suggestion".green().bold(), suggestion)?;
}
for note in &self.notes {
writeln!(f, " {} {}", "note:".yellow(), note)?;
}
Ok(())
}
}
fn get_line_column(source: &str, offset: u32) -> (usize, usize) {
let mut line = 1;
let mut column = 1;
let offset = offset as usize;
for (i, ch) in source.chars().enumerate() {
if i >= offset {
break;
}
if ch == '\n' {
line += 1;
column = 1;
} else {
column += 1;
}
}
(line, column)
}
fn get_source_line(source: &str, line_num: usize) -> Option<String> {
source
.lines()
.nth(line_num.saturating_sub(1))
.map(String::from)
}
fn add_automatic_suggestions(mut error: EnhancedError) -> EnhancedError {
let suggestion = match &error.error {
ErrorKind::UnsupportedFeature(feature) => {
let base_suggestion = format!(
"This Python feature '{}' is not yet supported. Consider using a simpler construct.",
feature
);
match feature.as_str() {
"yield" => Some((
"Generator functions are not supported. Consider returning a list instead."
.to_string(),
vec![
"Example: Instead of 'yield x', collect values and 'return values'"
.to_string(),
],
)),
"async for" => Some((
"Async iteration is not supported. Use regular iteration with async/await."
.to_string(),
vec![],
)),
"match" => Some((
"Pattern matching is not supported. Use if/elif/else chains instead."
.to_string(),
vec![],
)),
_ => Some((base_suggestion, vec![])),
}
}
ErrorKind::TypeInferenceError(msg) => {
if msg.contains("incompatible types") {
Some((
"Check that all type annotations are correct and consistent.".to_string(),
vec!["Rust has stricter type checking than Python".to_string()],
))
} else {
None
}
}
ErrorKind::InvalidTypeAnnotation(msg) => {
if msg.contains("borrow") {
Some((
"Consider using .clone() or restructuring to avoid multiple borrows."
.to_string(),
vec!["Rust's borrow checker ensures memory safety".to_string()],
))
} else {
None
}
}
_ => None,
};
if let Some((suggestion_text, notes)) = suggestion {
error = error.with_suggestion(&suggestion_text);
for note in notes {
error = error.add_note(¬e);
}
}
error
}
pub struct ErrorReporter {
errors: Vec<EnhancedError>,
source: String,
file_path: String,
}
impl ErrorReporter {
pub fn new(source: String, file_path: String) -> Self {
Self {
errors: Vec::new(),
source,
file_path,
}
}
pub fn report_error(&mut self, error: ErrorKind) {
let enhanced = EnhancedError::new(error);
self.errors.push(enhanced);
}
pub fn report_error_at<T: Ranged>(&mut self, error: ErrorKind, node: &T) {
let enhanced = EnhancedError::from_ast_node(error, node, &self.source).with_location(
&self.file_path,
0,
0,
); self.errors.push(enhanced);
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn display_errors(&self) {
for (i, error) in self.errors.iter().enumerate() {
if i > 0 {
println!();
}
println!("{}", error);
}
if self.errors.len() > 1 {
println!(
"\n{}: Found {} errors",
"summary".red().bold(),
self.errors.len()
);
}
}
pub fn into_result<T>(self, value: T) -> Result<T> {
if self.has_errors() {
self.display_errors();
anyhow::bail!("Transpilation failed with {} errors", self.errors.len())
} else {
Ok(value)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_enhanced_error_display() {
let error = EnhancedError::new(ErrorKind::UnsupportedFeature("yield".to_string()))
.with_location("test.py", 5, 10)
.with_source_line(" yield x")
.with_suggestion("Use return with a list instead")
.add_note("Generators are not supported");
let display = format!("{}", error);
assert!(display.contains("yield"));
assert!(display.contains("test.py:5:10"));
assert!(display.contains("yield x"));
assert!(display.contains("suggestion"));
}
#[test]
fn test_line_column_calculation() {
let source = "line1\nline2\nline3";
assert_eq!(get_line_column(source, 0), (1, 1));
assert_eq!(get_line_column(source, 6), (2, 1));
assert_eq!(get_line_column(source, 12), (3, 1));
}
#[test]
fn test_automatic_suggestions() {
let error = EnhancedError::new(ErrorKind::UnsupportedFeature("yield".to_string()));
let enhanced = add_automatic_suggestions(error);
assert!(enhanced.suggestion.is_some());
assert!(enhanced.suggestion.unwrap().contains("Generator"));
}
}