Skip to main content

depyler_analysis/
error_reporting.rs

1//! Enhanced error reporting with source location tracking and helpful suggestions
2
3use depyler_hir::error::ErrorKind;
4use anyhow::Result;
5use colored::Colorize;
6use rustpython_ast::Ranged;
7use std::fmt;
8
9/// Enhanced error with source location and context
10#[derive(Debug)]
11pub struct EnhancedError {
12    /// The base error
13    pub error: ErrorKind,
14    /// Source file path
15    pub file_path: Option<String>,
16    /// Line number (1-indexed)
17    pub line: Option<usize>,
18    /// Column number (1-indexed)
19    pub column: Option<usize>,
20    /// The source line containing the error
21    pub source_line: Option<String>,
22    /// Helpful suggestion for fixing the error
23    pub suggestion: Option<String>,
24    /// Related information
25    pub notes: Vec<String>,
26}
27
28impl EnhancedError {
29    pub fn new(error: ErrorKind) -> Self {
30        Self {
31            error,
32            file_path: None,
33            line: None,
34            column: None,
35            source_line: None,
36            suggestion: None,
37            notes: Vec::new(),
38        }
39    }
40
41    pub fn with_location(mut self, file: &str, line: usize, column: usize) -> Self {
42        self.file_path = Some(file.to_string());
43        self.line = Some(line);
44        self.column = Some(column);
45        self
46    }
47
48    pub fn with_source_line(mut self, line: &str) -> Self {
49        self.source_line = Some(line.to_string());
50        self
51    }
52
53    pub fn with_suggestion(mut self, suggestion: &str) -> Self {
54        self.suggestion = Some(suggestion.to_string());
55        self
56    }
57
58    pub fn add_note(mut self, note: &str) -> Self {
59        self.notes.push(note.to_string());
60        self
61    }
62
63    /// Extract location from AST node
64    pub fn from_ast_node<T: Ranged>(error: ErrorKind, node: &T, source: &str) -> Self {
65        let range = node.range();
66        let (line, column) = get_line_column(source, range.start().into());
67
68        let mut enhanced = Self::new(error).with_location("<input>", line, column);
69
70        // Extract the source line
71        if let Some(line_text) = get_source_line(source, line) {
72            enhanced = enhanced.with_source_line(&line_text);
73        }
74
75        // Add automatic suggestions based on error type
76        enhanced = add_automatic_suggestions(enhanced);
77
78        enhanced
79    }
80
81    /// Format location information (file:line:column)
82    #[inline]
83    fn format_location_info(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        if let (Some(file), Some(line), Some(column)) = (&self.file_path, self.line, self.column) {
85            writeln!(f, "  {} {}:{}:{}", "-->".blue().bold(), file, line, column)?;
86        }
87        Ok(())
88    }
89
90    /// Format source context with line number and pointer
91    #[inline]
92    fn format_source_context(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        if let (Some(line_text), Some(line_num), Some(col)) =
94            (&self.source_line, self.line, self.column)
95        {
96            writeln!(f, "   {} |", format!("{:4}", " ").dimmed())?;
97            writeln!(
98                f,
99                "   {} | {}",
100                format!("{:4}", line_num).blue().bold(),
101                line_text
102            )?;
103            writeln!(
104                f,
105                "   {} | {}{}",
106                format!("{:4}", " ").dimmed(),
107                " ".repeat(col.saturating_sub(1)),
108                "^".red().bold()
109            )?;
110        }
111        Ok(())
112    }
113
114    /// Format suggestion if present
115    #[inline]
116    fn format_suggestion(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        if let Some(suggestion) = &self.suggestion {
118            writeln!(f, "\n{}: {}", "suggestion".green().bold(), suggestion)?;
119        }
120        Ok(())
121    }
122
123    /// Format all notes
124    #[inline]
125    fn format_notes(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        for note in &self.notes {
127            writeln!(f, "  {} {}", "note:".yellow(), note)?;
128        }
129        Ok(())
130    }
131}
132
133impl fmt::Display for EnhancedError {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        // Error header
136        writeln!(f, "{}: {}", "error".red().bold(), self.error)?;
137
138        // Location info
139        self.format_location_info(f)?;
140
141        // Source context
142        self.format_source_context(f)?;
143
144        // Suggestion
145        self.format_suggestion(f)?;
146
147        // Notes
148        self.format_notes(f)?;
149
150        Ok(())
151    }
152}
153
154/// Get line and column from byte offset
155fn get_line_column(source: &str, offset: u32) -> (usize, usize) {
156    let mut line = 1;
157    let mut column = 1;
158    let offset = offset as usize;
159
160    for (i, ch) in source.chars().enumerate() {
161        if i >= offset {
162            break;
163        }
164        if ch == '\n' {
165            line += 1;
166            column = 1;
167        } else {
168            column += 1;
169        }
170    }
171
172    (line, column)
173}
174
175/// Extract a specific line from source
176fn get_source_line(source: &str, line_num: usize) -> Option<String> {
177    source
178        .lines()
179        .nth(line_num.saturating_sub(1))
180        .map(String::from)
181}
182
183/// Add automatic suggestions based on error type
184fn add_automatic_suggestions(error: EnhancedError) -> EnhancedError {
185    let suggestion = match &error.error {
186        ErrorKind::TypeMismatch {
187            expected,
188            found,
189            context,
190        } => generate_type_mismatch_suggestion(expected, found, context),
191        ErrorKind::UnsupportedFeature(feature) => suggest_unsupported_feature(feature),
192        ErrorKind::TypeInferenceError(msg) => suggest_type_inference_fix(msg),
193        ErrorKind::InvalidTypeAnnotation(msg) => suggest_annotation_fix(msg),
194        _ => None,
195    };
196
197    apply_suggestion_to_error(error, suggestion)
198}
199
200/// Apply a suggestion and notes to an error
201#[inline]
202fn apply_suggestion_to_error(
203    mut error: EnhancedError,
204    suggestion: Option<(String, Vec<String>)>,
205) -> EnhancedError {
206    if let Some((suggestion_text, notes)) = suggestion {
207        error = error.with_suggestion(&suggestion_text);
208        for note in notes {
209            error = error.add_note(&note);
210        }
211    }
212    error
213}
214
215/// Generate suggestion for unsupported Python features
216#[inline]
217fn suggest_unsupported_feature(feature: &str) -> Option<(String, Vec<String>)> {
218    // Special case for yield (most common unsupported feature)
219    if feature == "yield" {
220        return Some((
221            "Generator functions are not supported. Consider returning a list instead.".to_string(),
222            vec!["Example: Instead of 'yield x', collect values and 'return values'".to_string()],
223        ));
224    }
225
226    // Generic message for all other unsupported features
227    Some((
228        format!(
229            "This Python feature '{feature}' is not yet supported. Consider using a simpler construct."
230        ),
231        vec![],
232    ))
233}
234
235/// Generate suggestion for type inference errors
236#[inline]
237fn suggest_type_inference_fix(msg: &str) -> Option<(String, Vec<String>)> {
238    if msg.contains("incompatible types") {
239        Some((
240            "Check that all type annotations are correct and consistent.".to_string(),
241            vec!["Rust has stricter type checking than Python".to_string()],
242        ))
243    } else {
244        None
245    }
246}
247
248/// Generate suggestion for invalid type annotations
249#[inline]
250fn suggest_annotation_fix(msg: &str) -> Option<(String, Vec<String>)> {
251    if msg.contains("borrow") {
252        Some((
253            "Consider using .clone() or restructuring to avoid multiple borrows.".to_string(),
254            vec!["Rust's borrow checker ensures memory safety".to_string()],
255        ))
256    } else {
257        None
258    }
259}
260
261/// Generate helpful suggestions for type mismatches
262fn generate_type_mismatch_suggestion(
263    expected: &str,
264    found: &str,
265    context: &str,
266) -> Option<(String, Vec<String>)> {
267    // Try specific type mismatch patterns first
268    if let Some(suggestion) = suggest_string_mismatch(expected, found) {
269        return Some(suggestion);
270    }
271    if let Some(suggestion) = suggest_division_mismatch(expected, found) {
272        return Some(suggestion);
273    }
274    if let Some(suggestion) = suggest_option_mismatch(expected, found) {
275        return Some(suggestion);
276    }
277    if let Some(suggestion) = suggest_ownership_mismatch(expected, found) {
278        return Some(suggestion);
279    }
280    if let Some(suggestion) = suggest_collection_mismatch(expected, found) {
281        return Some(suggestion);
282    }
283
284    // Generic fallback for unmatched cases
285    suggest_generic_mismatch(expected, found, context)
286}
287
288/// Suggest fix for string type mismatches (str vs String, &str)
289#[inline]
290fn suggest_string_mismatch(expected: &str, found: &str) -> Option<(String, Vec<String>)> {
291    if (expected == "String" && found == "&str") || (expected == "str" && found == "String") {
292        Some((
293            "String type mismatch - Python 'str' maps to both Rust '&str' and 'String'".to_string(),
294            vec![
295                "In Rust:".to_string(),
296                "  • '&str' is a borrowed string slice (cheap, read-only)".to_string(),
297                "  • 'String' is an owned, heap-allocated string".to_string(),
298                "Python string methods (.upper(), .lower(), .strip()) return owned String"
299                    .to_string(),
300                "Use '.to_string()' to convert &str → String, or '&s' to convert String → &str"
301                    .to_string(),
302            ],
303        ))
304    } else {
305        None
306    }
307}
308
309/// Suggest fix for division type mismatches (int vs float)
310#[inline]
311fn suggest_division_mismatch(expected: &str, found: &str) -> Option<(String, Vec<String>)> {
312    if expected.contains("f64") && found.contains("i") {
313        Some((
314            "Division result type mismatch - Python '/' always returns float".to_string(),
315            vec![
316                "In Python: 10 / 3 = 3.333... (always float)".to_string(),
317                "In Rust: 10 / 3 = 3 (integer), 10.0 / 3.0 = 3.333... (float)".to_string(),
318                "Use '.as_f64()' or ensure operands are floats for division".to_string(),
319            ],
320        ))
321    } else {
322        None
323    }
324}
325
326/// Suggest fix for Option/None type mismatches
327#[inline]
328fn suggest_option_mismatch(expected: &str, found: &str) -> Option<(String, Vec<String>)> {
329    if expected.contains("Option") && (found == "None" || found == "()") {
330        Some((
331            "None type mismatch - Python None maps to Rust Option<T>".to_string(),
332            vec![
333                "In Rust, optional values use Option<T>:".to_string(),
334                "  • Some(value) for present values".to_string(),
335                "  • None for absent values".to_string(),
336                "Return type must be Option<T> if function can return None".to_string(),
337            ],
338        ))
339    } else {
340        None
341    }
342}
343
344/// Suggest fix for ownership mismatches (borrowed vs owned)
345#[inline]
346fn suggest_ownership_mismatch(expected: &str, found: &str) -> Option<(String, Vec<String>)> {
347    if expected.starts_with('&') && !found.starts_with('&') {
348        Some((
349            format!("Ownership mismatch - expected borrowed reference '{expected}', found owned value '{found}'"),
350            vec![
351                "Rust distinguishes between owned values and borrowed references".to_string(),
352                format!("Add '&' to borrow the value: '&{found}'"),
353                "Or use .as_ref() to get a reference without moving the value".to_string(),
354            ],
355        ))
356    } else {
357        None
358    }
359}
360
361/// Suggest fix for collection type mismatches (list vs Vec)
362#[inline]
363fn suggest_collection_mismatch(expected: &str, found: &str) -> Option<(String, Vec<String>)> {
364    if (expected.contains("Vec") && found.contains("list"))
365        || (expected.contains("list") && found.contains("Vec"))
366    {
367        Some((
368            "Collection type mismatch - Python list maps to Rust Vec<T>".to_string(),
369            vec![
370                "Python lists are dynamic arrays, similar to Rust Vec<T>".to_string(),
371                "Ensure element types match: Python list[int] → Rust Vec<i32>".to_string(),
372            ],
373        ))
374    } else {
375        None
376    }
377}
378
379/// Generic fallback suggestion for unmatched type mismatches
380#[inline]
381fn suggest_generic_mismatch(
382    expected: &str,
383    found: &str,
384    context: &str,
385) -> Option<(String, Vec<String>)> {
386    if context.contains("return") {
387        Some((
388            format!(
389                "Return type mismatch in {context}: expected '{expected}', found '{found}'"
390            ),
391            vec![
392                "Check that your function's return type annotation matches what you're actually returning".to_string(),
393                "Python and Rust may have different type representations".to_string(),
394            ],
395        ))
396    } else {
397        Some((
398            format!("Type mismatch in {context}: expected '{expected}', found '{found}'"),
399            vec![
400                "Rust's type system is stricter than Python's".to_string(),
401                "Ensure all type annotations are explicit and consistent".to_string(),
402            ],
403        ))
404    }
405}
406
407/// Error reporter that collects and displays enhanced errors
408pub struct ErrorReporter {
409    errors: Vec<EnhancedError>,
410    source: String,
411    file_path: String,
412}
413
414impl ErrorReporter {
415    pub fn new(source: String, file_path: String) -> Self {
416        Self {
417            errors: Vec::new(),
418            source,
419            file_path,
420        }
421    }
422
423    pub fn report_error(&mut self, error: ErrorKind) {
424        let enhanced = EnhancedError::new(error);
425        self.errors.push(enhanced);
426    }
427
428    pub fn report_error_at<T: Ranged>(&mut self, error: ErrorKind, node: &T) {
429        let enhanced = EnhancedError::from_ast_node(error, node, &self.source).with_location(
430            &self.file_path,
431            0,
432            0,
433        ); // Will be overridden by from_ast_node
434        self.errors.push(enhanced);
435    }
436
437    pub fn has_errors(&self) -> bool {
438        !self.errors.is_empty()
439    }
440
441    pub fn display_errors(&self) {
442        for (i, error) in self.errors.iter().enumerate() {
443            if i > 0 {
444                println!();
445            }
446            println!("{}", error);
447        }
448
449        if self.errors.len() > 1 {
450            println!(
451                "\n{}: Found {} errors",
452                "summary".red().bold(),
453                self.errors.len()
454            );
455        }
456    }
457
458    pub fn into_result<T>(self, value: T) -> Result<T> {
459        if self.has_errors() {
460            self.display_errors();
461            anyhow::bail!("Transpilation failed with {} errors", self.errors.len())
462        } else {
463            Ok(value)
464        }
465    }
466}
467
468#[cfg(test)]
469mod tests {
470    use super::*;
471
472    #[test]
473    fn test_enhanced_error_display() {
474        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("yield".to_string()))
475            .with_location("test.py", 5, 10)
476            .with_source_line("    yield x")
477            .with_suggestion("Use return with a list instead")
478            .add_note("Generators are not supported");
479
480        let display = format!("{}", error);
481        assert!(display.contains("yield"));
482        assert!(display.contains("test.py:5:10"));
483        assert!(display.contains("yield x"));
484        assert!(display.contains("suggestion"));
485    }
486
487    #[test]
488    fn test_line_column_calculation() {
489        let source = "line1\nline2\nline3";
490        assert_eq!(get_line_column(source, 0), (1, 1));
491        assert_eq!(get_line_column(source, 6), (2, 1));
492        assert_eq!(get_line_column(source, 12), (3, 1));
493    }
494
495    #[test]
496    fn test_automatic_suggestions() {
497        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("yield".to_string()));
498        let enhanced = add_automatic_suggestions(error);
499
500        assert!(enhanced.suggestion.is_some());
501        assert!(enhanced.suggestion.unwrap().contains("Generator"));
502    }
503
504    #[test]
505    fn test_string_type_mismatch_suggestion() {
506        let error = EnhancedError::new(ErrorKind::TypeMismatch {
507            expected: "String".to_string(),
508            found: "&str".to_string(),
509            context: "return type".to_string(),
510        });
511        let enhanced = add_automatic_suggestions(error);
512
513        assert!(enhanced.suggestion.is_some());
514        let suggestion = enhanced.suggestion.unwrap();
515        assert!(suggestion.contains("String type mismatch"));
516        assert!(!enhanced.notes.is_empty());
517        assert!(enhanced.notes.iter().any(|n| n.contains("&str")));
518    }
519
520    #[test]
521    fn test_division_type_mismatch_suggestion() {
522        let error = EnhancedError::new(ErrorKind::TypeMismatch {
523            expected: "f64".to_string(),
524            found: "i32".to_string(),
525            context: "division result".to_string(),
526        });
527        let enhanced = add_automatic_suggestions(error);
528
529        assert!(enhanced.suggestion.is_some());
530        let suggestion = enhanced.suggestion.unwrap();
531        assert!(suggestion.contains("Division result type mismatch"));
532        assert!(enhanced.notes.iter().any(|n| n.contains("always float")));
533    }
534
535    #[test]
536    fn test_option_type_mismatch_suggestion() {
537        let error = EnhancedError::new(ErrorKind::TypeMismatch {
538            expected: "Option<i32>".to_string(),
539            found: "None".to_string(),
540            context: "return value".to_string(),
541        });
542        let enhanced = add_automatic_suggestions(error);
543
544        assert!(enhanced.suggestion.is_some());
545        let suggestion = enhanced.suggestion.unwrap();
546        assert!(suggestion.contains("None type mismatch"));
547        assert!(enhanced.notes.iter().any(|n| n.contains("Option<T>")));
548    }
549
550    #[test]
551    fn test_ownership_mismatch_suggestion() {
552        let error = EnhancedError::new(ErrorKind::TypeMismatch {
553            expected: "&String".to_string(),
554            found: "String".to_string(),
555            context: "parameter".to_string(),
556        });
557        let enhanced = add_automatic_suggestions(error);
558
559        assert!(enhanced.suggestion.is_some());
560        let suggestion = enhanced.suggestion.unwrap();
561        assert!(suggestion.contains("Ownership mismatch"));
562        assert!(enhanced
563            .notes
564            .iter()
565            .any(|n| n.contains("borrowed reference")));
566    }
567
568    // ============================================================
569    // DEPYLER-COVERAGE-95: Additional comprehensive tests
570    // ============================================================
571
572    #[test]
573    fn test_enhanced_error_new() {
574        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("async".to_string()));
575        assert!(matches!(&error.error, ErrorKind::UnsupportedFeature(f) if f == "async"));
576        assert!(error.file_path.is_none());
577        assert!(error.line.is_none());
578        assert!(error.column.is_none());
579        assert!(error.source_line.is_none());
580        assert!(error.suggestion.is_none());
581        assert!(error.notes.is_empty());
582    }
583
584    #[test]
585    fn test_enhanced_error_with_location() {
586        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("test".to_string()))
587            .with_location("file.py", 10, 5);
588        assert_eq!(error.file_path, Some("file.py".to_string()));
589        assert_eq!(error.line, Some(10));
590        assert_eq!(error.column, Some(5));
591    }
592
593    #[test]
594    fn test_enhanced_error_with_source_line() {
595        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("test".to_string()))
596            .with_source_line("x = 10");
597        assert_eq!(error.source_line, Some("x = 10".to_string()));
598    }
599
600    #[test]
601    fn test_enhanced_error_with_suggestion() {
602        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("test".to_string()))
603            .with_suggestion("Try this instead");
604        assert_eq!(error.suggestion, Some("Try this instead".to_string()));
605    }
606
607    #[test]
608    fn test_enhanced_error_add_note() {
609        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("test".to_string()))
610            .add_note("Note 1")
611            .add_note("Note 2");
612        assert_eq!(error.notes.len(), 2);
613        assert_eq!(error.notes[0], "Note 1");
614        assert_eq!(error.notes[1], "Note 2");
615    }
616
617    #[test]
618    fn test_get_source_line_valid() {
619        let source = "line1\nline2\nline3";
620        assert_eq!(get_source_line(source, 1), Some("line1".to_string()));
621        assert_eq!(get_source_line(source, 2), Some("line2".to_string()));
622        assert_eq!(get_source_line(source, 3), Some("line3".to_string()));
623    }
624
625    #[test]
626    fn test_get_source_line_invalid() {
627        let source = "line1\nline2";
628        assert!(get_source_line(source, 10).is_none());
629        assert!(get_source_line(source, 0).is_some()); // saturating_sub prevents panic
630    }
631
632    #[test]
633    fn test_get_line_column_start_of_lines() {
634        let source = "ab\ncde\nfgh";
635        // Line 1 starts at offset 0
636        assert_eq!(get_line_column(source, 0), (1, 1));
637        // Line 2 starts at offset 3 (after "ab\n")
638        assert_eq!(get_line_column(source, 3), (2, 1));
639        // Line 3 starts at offset 7 (after "ab\ncde\n")
640        assert_eq!(get_line_column(source, 7), (3, 1));
641    }
642
643    #[test]
644    fn test_get_line_column_middle_of_line() {
645        let source = "hello world";
646        assert_eq!(get_line_column(source, 0), (1, 1));
647        assert_eq!(get_line_column(source, 5), (1, 6)); // 'w' position
648        assert_eq!(get_line_column(source, 10), (1, 11));
649    }
650
651    #[test]
652    fn test_get_line_column_end_of_source() {
653        let source = "abc";
654        assert_eq!(get_line_column(source, 100), (1, 4)); // Past end, still on line 1
655    }
656
657    #[test]
658    fn test_suggest_unsupported_feature_yield() {
659        let result = suggest_unsupported_feature("yield");
660        assert!(result.is_some());
661        let (suggestion, notes) = result.unwrap();
662        assert!(suggestion.contains("Generator"));
663        assert!(!notes.is_empty());
664    }
665
666    #[test]
667    fn test_suggest_unsupported_feature_other() {
668        let result = suggest_unsupported_feature("async");
669        assert!(result.is_some());
670        let (suggestion, _) = result.unwrap();
671        assert!(suggestion.contains("async"));
672        assert!(suggestion.contains("not yet supported"));
673    }
674
675    #[test]
676    fn test_suggest_type_inference_fix_incompatible() {
677        let result = suggest_type_inference_fix("incompatible types in expression");
678        assert!(result.is_some());
679        let (suggestion, notes) = result.unwrap();
680        assert!(suggestion.contains("type annotations"));
681        assert!(notes.iter().any(|n| n.contains("Rust")));
682    }
683
684    #[test]
685    fn test_suggest_type_inference_fix_other() {
686        let result = suggest_type_inference_fix("unknown type");
687        assert!(result.is_none());
688    }
689
690    #[test]
691    fn test_suggest_annotation_fix_borrow() {
692        let result = suggest_annotation_fix("cannot borrow twice");
693        assert!(result.is_some());
694        let (suggestion, notes) = result.unwrap();
695        assert!(suggestion.contains(".clone()"));
696        assert!(notes.iter().any(|n| n.contains("borrow checker")));
697    }
698
699    #[test]
700    fn test_suggest_annotation_fix_other() {
701        let result = suggest_annotation_fix("unknown annotation issue");
702        assert!(result.is_none());
703    }
704
705    #[test]
706    fn test_suggest_collection_mismatch_vec_list() {
707        let result = suggest_collection_mismatch("Vec<i32>", "list[int]");
708        assert!(result.is_some());
709        let (suggestion, notes) = result.unwrap();
710        assert!(suggestion.contains("Collection type mismatch"));
711        assert!(notes.iter().any(|n| n.contains("dynamic arrays")));
712    }
713
714    #[test]
715    fn test_suggest_collection_mismatch_list_vec() {
716        let result = suggest_collection_mismatch("list[str]", "Vec<String>");
717        assert!(result.is_some());
718    }
719
720    #[test]
721    fn test_suggest_collection_mismatch_no_match() {
722        let result = suggest_collection_mismatch("int", "float");
723        assert!(result.is_none());
724    }
725
726    #[test]
727    fn test_suggest_generic_mismatch_return() {
728        let result = suggest_generic_mismatch("int", "str", "return statement");
729        assert!(result.is_some());
730        let (suggestion, notes) = result.unwrap();
731        assert!(suggestion.contains("Return type mismatch"));
732        assert!(notes.iter().any(|n| n.contains("return type annotation")));
733    }
734
735    #[test]
736    fn test_suggest_generic_mismatch_non_return() {
737        let result = suggest_generic_mismatch("int", "str", "assignment");
738        assert!(result.is_some());
739        let (suggestion, notes) = result.unwrap();
740        assert!(suggestion.contains("Type mismatch in assignment"));
741        assert!(notes.iter().any(|n| n.contains("stricter")));
742    }
743
744    #[test]
745    fn test_suggest_string_mismatch_str_to_string() {
746        let result = suggest_string_mismatch("str", "String");
747        assert!(result.is_some());
748        let (suggestion, notes) = result.unwrap();
749        assert!(suggestion.contains("String type mismatch"));
750        assert!(notes.iter().any(|n| n.contains(".to_string()")));
751    }
752
753    #[test]
754    fn test_suggest_string_mismatch_no_match() {
755        let result = suggest_string_mismatch("int", "float");
756        assert!(result.is_none());
757    }
758
759    #[test]
760    fn test_suggest_division_mismatch_no_match() {
761        let result = suggest_division_mismatch("String", "i32");
762        assert!(result.is_none());
763    }
764
765    #[test]
766    fn test_suggest_option_mismatch_with_unit() {
767        let result = suggest_option_mismatch("Option<String>", "()");
768        assert!(result.is_some());
769        let (suggestion, _) = result.unwrap();
770        assert!(suggestion.contains("None type mismatch"));
771    }
772
773    #[test]
774    fn test_suggest_option_mismatch_no_match() {
775        let result = suggest_option_mismatch("String", "int");
776        assert!(result.is_none());
777    }
778
779    #[test]
780    fn test_suggest_ownership_mismatch_no_match() {
781        let result = suggest_ownership_mismatch("String", "String");
782        assert!(result.is_none());
783    }
784
785    #[test]
786    fn test_apply_suggestion_to_error_with_suggestion() {
787        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("test".to_string()));
788        let suggestion = Some((
789            "Use this".to_string(),
790            vec!["Note 1".to_string(), "Note 2".to_string()],
791        ));
792        let result = apply_suggestion_to_error(error, suggestion);
793        assert_eq!(result.suggestion, Some("Use this".to_string()));
794        assert_eq!(result.notes.len(), 2);
795    }
796
797    #[test]
798    fn test_apply_suggestion_to_error_without_suggestion() {
799        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("test".to_string()));
800        let result = apply_suggestion_to_error(error, None);
801        assert!(result.suggestion.is_none());
802        assert!(result.notes.is_empty());
803    }
804
805    #[test]
806    fn test_error_reporter_new() {
807        let reporter = ErrorReporter::new("source code".to_string(), "test.py".to_string());
808        assert!(!reporter.has_errors());
809    }
810
811    #[test]
812    fn test_error_reporter_report_error() {
813        let mut reporter = ErrorReporter::new("source".to_string(), "test.py".to_string());
814        reporter.report_error(ErrorKind::UnsupportedFeature("async".to_string()));
815        assert!(reporter.has_errors());
816    }
817
818    #[test]
819    fn test_error_reporter_has_errors_false() {
820        let reporter = ErrorReporter::new("source".to_string(), "test.py".to_string());
821        assert!(!reporter.has_errors());
822    }
823
824    #[test]
825    fn test_error_reporter_has_errors_true() {
826        let mut reporter = ErrorReporter::new("source".to_string(), "test.py".to_string());
827        reporter.report_error(ErrorKind::UnsupportedFeature("test".to_string()));
828        assert!(reporter.has_errors());
829    }
830
831    #[test]
832    fn test_error_reporter_into_result_success() {
833        let reporter = ErrorReporter::new("source".to_string(), "test.py".to_string());
834        let result = reporter.into_result(42);
835        assert!(result.is_ok());
836        assert_eq!(result.unwrap(), 42);
837    }
838
839    #[test]
840    fn test_error_reporter_into_result_failure() {
841        let mut reporter = ErrorReporter::new("source".to_string(), "test.py".to_string());
842        reporter.report_error(ErrorKind::UnsupportedFeature("test".to_string()));
843        let result = reporter.into_result::<i32>(42);
844        assert!(result.is_err());
845    }
846
847    #[test]
848    fn test_enhanced_error_display_no_location() {
849        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("test".to_string()));
850        let display = format!("{}", error);
851        // ErrorKind::UnsupportedFeature displays as "Unsupported Python feature"
852        assert!(display.contains("Unsupported Python feature"));
853        // Should not contain location info
854        assert!(!display.contains("-->"));
855    }
856
857    #[test]
858    fn test_enhanced_error_display_with_notes() {
859        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("test".to_string()))
860            .add_note("First note")
861            .add_note("Second note");
862        let display = format!("{}", error);
863        assert!(display.contains("First note"));
864        assert!(display.contains("Second note"));
865    }
866
867    #[test]
868    fn test_add_automatic_suggestions_type_inference() {
869        let error = EnhancedError::new(ErrorKind::TypeInferenceError(
870            "incompatible types".to_string(),
871        ));
872        let enhanced = add_automatic_suggestions(error);
873        assert!(enhanced.suggestion.is_some());
874    }
875
876    #[test]
877    fn test_add_automatic_suggestions_invalid_annotation() {
878        let error = EnhancedError::new(ErrorKind::InvalidTypeAnnotation(
879            "cannot borrow".to_string(),
880        ));
881        let enhanced = add_automatic_suggestions(error);
882        assert!(enhanced.suggestion.is_some());
883    }
884
885    #[test]
886    fn test_add_automatic_suggestions_other_error() {
887        // ParseError is a unit variant, not a tuple variant
888        let error = EnhancedError::new(ErrorKind::ParseError);
889        let enhanced = add_automatic_suggestions(error);
890        // ParseError doesn't have automatic suggestions
891        assert!(enhanced.suggestion.is_none());
892    }
893
894    #[test]
895    fn test_generate_type_mismatch_suggestion_string() {
896        let result = generate_type_mismatch_suggestion("String", "&str", "return type");
897        assert!(result.is_some());
898    }
899
900    #[test]
901    fn test_generate_type_mismatch_suggestion_division() {
902        let result = generate_type_mismatch_suggestion("f64", "i32", "division");
903        assert!(result.is_some());
904    }
905
906    #[test]
907    fn test_generate_type_mismatch_suggestion_option() {
908        let result = generate_type_mismatch_suggestion("Option<i32>", "None", "return");
909        assert!(result.is_some());
910    }
911
912    #[test]
913    fn test_generate_type_mismatch_suggestion_ownership() {
914        let result = generate_type_mismatch_suggestion("&str", "String", "parameter");
915        assert!(result.is_some());
916    }
917
918    #[test]
919    fn test_generate_type_mismatch_suggestion_collection() {
920        let result = generate_type_mismatch_suggestion("Vec<i32>", "list[int]", "assignment");
921        assert!(result.is_some());
922    }
923
924    #[test]
925    fn test_generate_type_mismatch_suggestion_fallback() {
926        let result = generate_type_mismatch_suggestion("CustomType", "OtherType", "expression");
927        assert!(result.is_some());
928        let (suggestion, _) = result.unwrap();
929        assert!(suggestion.contains("Type mismatch"));
930    }
931
932    #[test]
933    fn test_enhanced_error_chaining() {
934        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("test".to_string()))
935            .with_location("file.py", 1, 1)
936            .with_source_line("test code")
937            .with_suggestion("Try this")
938            .add_note("Note 1")
939            .add_note("Note 2");
940
941        assert_eq!(error.file_path, Some("file.py".to_string()));
942        assert_eq!(error.line, Some(1));
943        assert_eq!(error.column, Some(1));
944        assert_eq!(error.source_line, Some("test code".to_string()));
945        assert_eq!(error.suggestion, Some("Try this".to_string()));
946        assert_eq!(error.notes.len(), 2);
947    }
948
949    #[test]
950    fn test_enhanced_error_display_full() {
951        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("test".to_string()))
952            .with_location("file.py", 5, 10)
953            .with_source_line("    test_code()")
954            .with_suggestion("Use something else")
955            .add_note("Important note");
956
957        let display = format!("{}", error);
958        assert!(display.contains("error"));
959        assert!(display.contains("file.py:5:10"));
960        assert!(display.contains("test_code()"));
961        assert!(display.contains("suggestion"));
962        assert!(display.contains("note:"));
963    }
964
965    #[test]
966    fn test_get_line_column_empty_source() {
967        let source = "";
968        assert_eq!(get_line_column(source, 0), (1, 1));
969    }
970
971    #[test]
972    fn test_get_source_line_empty_source() {
973        let source = "";
974        assert!(get_source_line(source, 1).is_none());
975    }
976
977    #[test]
978    fn test_get_source_line_single_line() {
979        let source = "only one line";
980        assert_eq!(
981            get_source_line(source, 1),
982            Some("only one line".to_string())
983        );
984        assert!(get_source_line(source, 2).is_none());
985    }
986
987    #[test]
988    fn test_error_reporter_display_errors_single() {
989        let mut reporter = ErrorReporter::new("source".to_string(), "test.py".to_string());
990        reporter.report_error(ErrorKind::UnsupportedFeature("async".to_string()));
991        reporter.display_errors();
992        assert!(reporter.has_errors());
993    }
994
995    #[test]
996    fn test_error_reporter_display_errors_multiple() {
997        let mut reporter = ErrorReporter::new("source".to_string(), "test.py".to_string());
998        reporter.report_error(ErrorKind::UnsupportedFeature("async".to_string()));
999        reporter.report_error(ErrorKind::ParseError);
1000        reporter.report_error(ErrorKind::TypeInferenceError("test".to_string()));
1001        reporter.display_errors();
1002        assert_eq!(reporter.errors.len(), 3);
1003    }
1004
1005    #[test]
1006    fn test_error_reporter_display_errors_empty() {
1007        let reporter = ErrorReporter::new("source".to_string(), "test.py".to_string());
1008        reporter.display_errors();
1009        assert!(!reporter.has_errors());
1010    }
1011
1012    #[test]
1013    fn test_enhanced_error_format_location_info_complete() {
1014        let error = EnhancedError::new(ErrorKind::ParseError)
1015            .with_location("test.py", 10, 20);
1016        let display = format!("{}", error);
1017        assert!(display.contains("-->"));
1018        assert!(display.contains("test.py:10:20"));
1019    }
1020
1021    #[test]
1022    fn test_enhanced_error_format_location_info_incomplete() {
1023        let mut error = EnhancedError::new(ErrorKind::ParseError);
1024        error.file_path = Some("test.py".to_string());
1025        error.line = Some(10);
1026        let display = format!("{}", error);
1027        assert!(!display.contains("-->"));
1028    }
1029
1030    #[test]
1031    fn test_enhanced_error_format_source_context_complete() {
1032        let error = EnhancedError::new(ErrorKind::ParseError)
1033            .with_location("test.py", 5, 10)
1034            .with_source_line("    x = 10");
1035        let display = format!("{}", error);
1036        assert!(display.contains("x = 10"));
1037        assert!(display.contains("|"));
1038    }
1039
1040    #[test]
1041    fn test_enhanced_error_format_source_context_incomplete() {
1042        let mut error = EnhancedError::new(ErrorKind::ParseError);
1043        error.source_line = Some("x = 10".to_string());
1044        error.line = Some(5);
1045        let display = format!("{}", error);
1046        assert!(!display.contains("|"));
1047    }
1048
1049    #[test]
1050    fn test_enhanced_error_format_suggestion_present() {
1051        let error = EnhancedError::new(ErrorKind::ParseError)
1052            .with_suggestion("Try using a different syntax");
1053        let display = format!("{}", error);
1054        assert!(display.contains("suggestion"));
1055        assert!(display.contains("Try using a different syntax"));
1056    }
1057
1058    #[test]
1059    fn test_enhanced_error_format_suggestion_absent() {
1060        let error = EnhancedError::new(ErrorKind::ParseError);
1061        let display = format!("{}", error);
1062        assert!(!display.contains("suggestion"));
1063    }
1064
1065    #[test]
1066    fn test_enhanced_error_format_notes_present() {
1067        let error = EnhancedError::new(ErrorKind::ParseError)
1068            .add_note("First note")
1069            .add_note("Second note");
1070        let display = format!("{}", error);
1071        assert!(display.contains("note:"));
1072        assert!(display.contains("First note"));
1073        assert!(display.contains("Second note"));
1074    }
1075
1076    #[test]
1077    fn test_enhanced_error_format_notes_absent() {
1078        let error = EnhancedError::new(ErrorKind::ParseError);
1079        let display = format!("{}", error);
1080        let note_count = display.matches("note:").count();
1081        assert_eq!(note_count, 0);
1082    }
1083
1084    #[test]
1085    fn test_generate_type_mismatch_suggestion_multiple_patterns() {
1086        let result1 = generate_type_mismatch_suggestion("String", "&str", "context");
1087        assert!(result1.is_some());
1088
1089        let result2 = generate_type_mismatch_suggestion("f64", "i32", "context");
1090        assert!(result2.is_some());
1091
1092        let result3 = generate_type_mismatch_suggestion("Option<T>", "None", "context");
1093        assert!(result3.is_some());
1094
1095        let result4 = generate_type_mismatch_suggestion("&String", "String", "context");
1096        assert!(result4.is_some());
1097
1098        let result5 = generate_type_mismatch_suggestion("Vec<i32>", "list", "context");
1099        assert!(result5.is_some());
1100    }
1101
1102    #[test]
1103    fn test_suggest_division_mismatch_with_various_int_types() {
1104        assert!(suggest_division_mismatch("f64", "i8").is_some());
1105        assert!(suggest_division_mismatch("f64", "i16").is_some());
1106        assert!(suggest_division_mismatch("f64", "i32").is_some());
1107        assert!(suggest_division_mismatch("f64", "i64").is_some());
1108        assert!(suggest_division_mismatch("f32", "i32").is_none());
1109    }
1110
1111    #[test]
1112    fn test_get_line_column_multiline_with_various_offsets() {
1113        let source = "abc\ndef\nghi";
1114        assert_eq!(get_line_column(source, 0), (1, 1));
1115        assert_eq!(get_line_column(source, 1), (1, 2));
1116        assert_eq!(get_line_column(source, 2), (1, 3));
1117        assert_eq!(get_line_column(source, 3), (1, 4));
1118        assert_eq!(get_line_column(source, 4), (2, 1));
1119        assert_eq!(get_line_column(source, 5), (2, 2));
1120    }
1121
1122    #[test]
1123    fn test_get_source_line_with_multiple_lines() {
1124        let source = "first\nsecond\nthird\nfourth";
1125        assert_eq!(get_source_line(source, 1), Some("first".to_string()));
1126        assert_eq!(get_source_line(source, 2), Some("second".to_string()));
1127        assert_eq!(get_source_line(source, 3), Some("third".to_string()));
1128        assert_eq!(get_source_line(source, 4), Some("fourth".to_string()));
1129    }
1130
1131    #[test]
1132    fn test_enhanced_error_multiple_notes() {
1133        let error = EnhancedError::new(ErrorKind::ParseError)
1134            .add_note("Note 1")
1135            .add_note("Note 2")
1136            .add_note("Note 3");
1137        assert_eq!(error.notes.len(), 3);
1138        let display = format!("{}", error);
1139        assert!(display.contains("Note 1"));
1140        assert!(display.contains("Note 2"));
1141        assert!(display.contains("Note 3"));
1142    }
1143
1144    #[test]
1145    fn test_error_reporter_multiple_operations() {
1146        let mut reporter = ErrorReporter::new("code\nmore code".to_string(), "file.py".to_string());
1147        assert!(!reporter.has_errors());
1148
1149        reporter.report_error(ErrorKind::ParseError);
1150        assert!(reporter.has_errors());
1151
1152        reporter.report_error(ErrorKind::UnsupportedFeature("test".to_string()));
1153        assert_eq!(reporter.errors.len(), 2);
1154    }
1155
1156    #[test]
1157    fn test_add_automatic_suggestions_type_mismatch() {
1158        let error = EnhancedError::new(ErrorKind::TypeMismatch {
1159            expected: "String".to_string(),
1160            found: "&str".to_string(),
1161            context: "test".to_string(),
1162        });
1163        let enhanced = add_automatic_suggestions(error);
1164        assert!(enhanced.suggestion.is_some());
1165        assert!(!enhanced.notes.is_empty());
1166    }
1167
1168    #[test]
1169    fn test_add_automatic_suggestions_unsupported_feature_non_yield() {
1170        let error = EnhancedError::new(ErrorKind::UnsupportedFeature("walrus".to_string()));
1171        let enhanced = add_automatic_suggestions(error);
1172        assert!(enhanced.suggestion.is_some());
1173        assert!(enhanced.suggestion.unwrap().contains("walrus"));
1174    }
1175
1176    #[test]
1177    fn test_enhanced_error_display_with_column_edge_cases() {
1178        let error = EnhancedError::new(ErrorKind::ParseError)
1179            .with_location("test.py", 1, 1)
1180            .with_source_line("x");
1181        let display = format!("{}", error);
1182        assert!(display.contains("^"));
1183    }
1184
1185    #[test]
1186    fn test_enhanced_error_display_with_large_column() {
1187        let error = EnhancedError::new(ErrorKind::ParseError)
1188            .with_location("test.py", 1, 100)
1189            .with_source_line("short line");
1190        let display = format!("{}", error);
1191        assert!(display.contains("^"));
1192    }
1193
1194    #[test]
1195    fn test_get_line_column_unicode() {
1196        let source = "hello\n世界\ntest";
1197        assert_eq!(get_line_column(source, 0), (1, 1));
1198        assert_eq!(get_line_column(source, 6), (2, 1));
1199        assert_eq!(get_line_column(source, 7), (2, 2));
1200    }
1201
1202    #[test]
1203    fn test_suggest_ownership_mismatch_with_ampersand() {
1204        let result = suggest_ownership_mismatch("&Vec<i32>", "Vec<i32>");
1205        assert!(result.is_some());
1206        let (suggestion, notes) = result.unwrap();
1207        assert!(suggestion.contains("Ownership mismatch"));
1208        assert!(notes.iter().any(|n| n.contains("borrowed")));
1209    }
1210
1211    #[test]
1212    fn test_suggest_generic_mismatch_with_return_context() {
1213        let result = suggest_generic_mismatch("A", "B", "return value");
1214        assert!(result.is_some());
1215        let (suggestion, _) = result.unwrap();
1216        assert!(suggestion.contains("Return type mismatch"));
1217    }
1218
1219    #[test]
1220    fn test_error_reporter_into_result_with_value() {
1221        let reporter = ErrorReporter::new("source".to_string(), "test.py".to_string());
1222        let result = reporter.into_result("success");
1223        assert!(result.is_ok());
1224        assert_eq!(result.unwrap(), "success");
1225    }
1226
1227    #[test]
1228    fn test_error_reporter_into_result_with_errors() {
1229        let mut reporter = ErrorReporter::new("source".to_string(), "test.py".to_string());
1230        reporter.report_error(ErrorKind::ParseError);
1231        let result = reporter.into_result::<&str>("value");
1232        assert!(result.is_err());
1233        assert!(result.unwrap_err().to_string().contains("1 error"));
1234    }
1235
1236    #[test]
1237    fn test_enhanced_error_debug_output() {
1238        let error = EnhancedError::new(ErrorKind::ParseError)
1239            .with_location("test.py", 1, 1);
1240        let debug = format!("{:?}", error);
1241        assert!(debug.contains("EnhancedError"));
1242    }
1243
1244    #[test]
1245    fn test_suggest_annotation_fix_with_borrow_keyword() {
1246        let result = suggest_annotation_fix("cannot borrow as mutable");
1247        assert!(result.is_some());
1248        let (suggestion, _) = result.unwrap();
1249        assert!(suggestion.contains(".clone()"));
1250    }
1251
1252    #[test]
1253    fn test_suggest_type_inference_fix_no_match() {
1254        let result = suggest_type_inference_fix("some other error");
1255        assert!(result.is_none());
1256    }
1257
1258    #[test]
1259    fn test_enhanced_error_all_fields_populated() {
1260        let error = EnhancedError::new(ErrorKind::TypeMismatch {
1261            expected: "i32".to_string(),
1262            found: "str".to_string(),
1263            context: "assignment".to_string(),
1264        })
1265        .with_location("file.py", 42, 17)
1266        .with_source_line("    result = \"text\"")
1267        .with_suggestion("Convert to the correct type")
1268        .add_note("First note")
1269        .add_note("Second note");
1270
1271        assert!(error.file_path.is_some());
1272        assert!(error.line.is_some());
1273        assert!(error.column.is_some());
1274        assert!(error.source_line.is_some());
1275        assert!(error.suggestion.is_some());
1276        assert_eq!(error.notes.len(), 2);
1277    }
1278
1279    #[test]
1280    fn test_error_reporter_with_empty_source() {
1281        let reporter = ErrorReporter::new(String::new(), "empty.py".to_string());
1282        assert!(!reporter.has_errors());
1283        assert_eq!(reporter.source, "");
1284    }
1285
1286    #[test]
1287    fn test_get_source_line_zero_line() {
1288        let source = "line1\nline2\nline3";
1289        let result = get_source_line(source, 0);
1290        assert!(result.is_some());
1291    }
1292
1293    #[test]
1294    fn test_enhanced_error_partial_fields() {
1295        let mut error = EnhancedError::new(ErrorKind::ParseError);
1296        error.file_path = Some("test.py".to_string());
1297        error.column = Some(5);
1298        let display = format!("{}", error);
1299        assert!(!display.contains("-->"));
1300    }
1301
1302    #[test]
1303    fn test_from_ast_node_with_simple_source() {
1304        use rustpython_ast::Suite;
1305        use rustpython_parser::Parse;
1306
1307        let source = "x = 1\ny = 2\nz = 3";
1308        let parsed = Suite::parse(source, "<test>").unwrap();
1309
1310        if let Some(stmt) = parsed.first() {
1311            let error = EnhancedError::from_ast_node(
1312                ErrorKind::UnsupportedFeature("test".to_string()),
1313                stmt,
1314                source,
1315            );
1316
1317            assert_eq!(error.file_path, Some("<input>".to_string()));
1318            assert!(error.line.is_some());
1319            assert!(error.column.is_some());
1320            assert!(error.source_line.is_some());
1321        }
1322    }
1323
1324    #[test]
1325    fn test_from_ast_node_with_multiline_source() {
1326        use rustpython_ast::Suite;
1327        use rustpython_parser::Parse;
1328
1329        let source = "def foo():\n    pass\n\nclass Bar:\n    pass";
1330        let parsed = Suite::parse(source, "<test>").unwrap();
1331
1332        if let Some(stmt) = parsed.get(1) {
1333            let error = EnhancedError::from_ast_node(
1334                ErrorKind::TypeMismatch {
1335                    expected: "int".to_string(),
1336                    found: "str".to_string(),
1337                    context: "test".to_string(),
1338                },
1339                stmt,
1340                source,
1341            );
1342
1343            assert!(error.line.is_some());
1344            assert!(error.column.is_some());
1345        }
1346    }
1347
1348    #[test]
1349    fn test_report_error_at_with_ast_node() {
1350        use rustpython_ast::Suite;
1351        use rustpython_parser::Parse;
1352
1353        let source = "x = 1 + 2";
1354        let parsed = Suite::parse(source, "<test>").unwrap();
1355
1356        let mut reporter = ErrorReporter::new(source.to_string(), "test.py".to_string());
1357
1358        if let Some(stmt) = parsed.first() {
1359            reporter.report_error_at(ErrorKind::UnsupportedFeature("addition".to_string()), stmt);
1360            assert!(reporter.has_errors());
1361            assert_eq!(reporter.errors.len(), 1);
1362        }
1363    }
1364
1365    #[test]
1366    fn test_error_reporter_multiple_report_error_at() {
1367        use rustpython_ast::Suite;
1368        use rustpython_parser::Parse;
1369
1370        let source = "x = 1\ny = 2\nz = 3";
1371        let parsed = Suite::parse(source, "<test>").unwrap();
1372
1373        let mut reporter = ErrorReporter::new(source.to_string(), "multi.py".to_string());
1374
1375        for stmt in &parsed {
1376            reporter.report_error_at(ErrorKind::ParseError, stmt);
1377        }
1378
1379        assert_eq!(reporter.errors.len(), 3);
1380    }
1381
1382    #[test]
1383    fn test_enhanced_error_column_saturation() {
1384        let error = EnhancedError::new(ErrorKind::ParseError)
1385            .with_location("test.py", 1, 0)
1386            .with_source_line("test");
1387        let display = format!("{}", error);
1388        assert!(display.contains("^"));
1389    }
1390
1391    #[test]
1392    fn test_get_line_column_at_newline() {
1393        let source = "line1\nline2";
1394        assert_eq!(get_line_column(source, 5), (1, 6));
1395    }
1396
1397    #[test]
1398    fn test_error_kinds_with_automatic_suggestions() {
1399        let error1 = EnhancedError::new(ErrorKind::CodeGenerationError("test".to_string()));
1400        let enhanced1 = add_automatic_suggestions(error1);
1401        assert!(enhanced1.suggestion.is_none());
1402
1403        let error2 = EnhancedError::new(ErrorKind::VerificationError("test".to_string()));
1404        let enhanced2 = add_automatic_suggestions(error2);
1405        assert!(enhanced2.suggestion.is_none());
1406    }
1407
1408    #[test]
1409    fn test_enhanced_error_display_minimal() {
1410        let error = EnhancedError::new(ErrorKind::InternalError("minimal".to_string()));
1411        let display = format!("{}", error);
1412        assert!(display.contains("error"));
1413        assert!(display.contains("Internal error"));
1414    }
1415
1416    #[test]
1417    fn test_format_methods_edge_cases() {
1418        let mut error = EnhancedError::new(ErrorKind::ParseError);
1419        error.file_path = Some("test.py".to_string());
1420        error.line = None;
1421        error.column = Some(10);
1422        let display = format!("{}", error);
1423        assert!(!display.contains("-->"));
1424
1425        error.line = Some(5);
1426        error.column = None;
1427        let display2 = format!("{}", error);
1428        assert!(!display2.contains("-->"));
1429    }
1430}