Skip to main content

torsh_jit/
error_diagnostics.rs

1//! Error diagnostics for JIT compilation
2//!
3//! This module provides comprehensive error diagnostics capabilities for JIT compilation,
4//! including detailed error messages, source location tracking, and recovery suggestions.
5
6// Framework infrastructure - components designed for future use
7#![allow(dead_code)]
8#![allow(unexpected_cfgs)]
9use crate::{JitError, JitResult};
10use std::collections::HashMap;
11use std::fmt;
12use std::time::Instant;
13
14/// Error diagnostics manager
15#[derive(Debug)]
16pub struct ErrorDiagnosticsManager {
17    /// Diagnostic configuration
18    config: DiagnosticsConfig,
19
20    /// Error history
21    error_history: Vec<DiagnosticError>,
22
23    /// Error patterns for analysis
24    error_patterns: HashMap<String, ErrorPattern>,
25
26    /// Recovery suggestions database
27    recovery_suggestions: HashMap<ErrorCategory, Vec<RecoverySuggestion>>,
28
29    /// Context information
30    context_stack: Vec<DiagnosticContext>,
31
32    /// Statistics
33    stats: DiagnosticsStats,
34}
35
36/// Diagnostic error with enhanced information
37#[derive(Debug)]
38pub struct DiagnosticError {
39    /// Unique error ID
40    pub id: String,
41
42    /// Error timestamp
43    pub timestamp: Instant,
44
45    /// Error category
46    pub category: ErrorCategory,
47
48    /// Error severity
49    pub severity: ErrorSeverity,
50
51    /// Error message
52    pub message: String,
53
54    /// Source location
55    pub source_location: Option<SourceLocation>,
56
57    /// Stack trace
58    pub stack_trace: Vec<StackFrame>,
59
60    /// Error context
61    pub context: DiagnosticContext,
62
63    /// Related errors
64    pub related_errors: Vec<String>,
65
66    /// Recovery suggestions
67    pub suggestions: Vec<RecoverySuggestion>,
68
69    /// Error metadata
70    pub metadata: HashMap<String, String>,
71
72    /// Underlying error
73    pub underlying_error: Option<Box<JitError>>,
74}
75
76/// Error categories for classification
77#[derive(Debug, Clone, PartialEq, Eq, Hash)]
78pub enum ErrorCategory {
79    /// Graph construction errors
80    GraphConstruction,
81
82    /// Type inference errors
83    TypeInference,
84
85    /// Shape inference errors
86    ShapeInference,
87
88    /// Optimization errors
89    Optimization,
90
91    /// Code generation errors
92    CodeGeneration,
93
94    /// Runtime errors
95    Runtime,
96
97    /// Memory errors
98    Memory,
99
100    /// Resource errors
101    Resource,
102
103    /// User input errors
104    UserInput,
105
106    /// Internal compiler errors
107    Internal,
108
109    /// External dependency errors
110    External,
111}
112
113/// Error severity levels
114#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
115pub enum ErrorSeverity {
116    /// Information messages
117    Info,
118
119    /// Warning messages
120    Warning,
121
122    /// Error messages
123    Error,
124
125    /// Fatal error messages
126    Fatal,
127
128    /// Internal compiler error
129    Ice, // Internal Compiler Error
130}
131
132/// Source location for diagnostics
133#[derive(Debug, Clone)]
134pub struct SourceLocation {
135    /// File path
136    pub file: String,
137
138    /// Line number (1-based)
139    pub line: u32,
140
141    /// Column number (1-based)
142    pub column: u32,
143
144    /// Length of the error span
145    pub length: Option<u32>,
146
147    /// Source code snippet
148    pub snippet: Option<String>,
149}
150
151/// Stack frame for error traces
152#[derive(Debug, Clone)]
153pub struct StackFrame {
154    /// Function name
155    pub function: String,
156
157    /// File path
158    pub file: Option<String>,
159
160    /// Line number
161    pub line: Option<u32>,
162
163    /// Address
164    pub address: Option<u64>,
165
166    /// Module name
167    pub module: Option<String>,
168}
169
170/// Diagnostic context
171#[derive(Debug, Clone)]
172pub struct DiagnosticContext {
173    /// Operation being performed
174    pub operation: String,
175
176    /// Input description
177    pub input: String,
178
179    /// Expected result
180    pub expected: Option<String>,
181
182    /// Actual result
183    pub actual: Option<String>,
184
185    /// Environment information
186    pub environment: EnvironmentInfo,
187
188    /// Additional context data
189    pub data: HashMap<String, String>,
190}
191
192/// Environment information
193#[derive(Debug, Clone)]
194pub struct EnvironmentInfo {
195    /// Rust version
196    pub rust_version: String,
197
198    /// ToRSh version
199    pub torsh_version: String,
200
201    /// Target architecture
202    pub target_arch: String,
203
204    /// Operating system
205    pub target_os: String,
206
207    /// Available memory
208    pub available_memory: Option<u64>,
209
210    /// CPU information
211    pub cpu_info: Option<String>,
212
213    /// GPU information
214    pub gpu_info: Option<String>,
215}
216
217/// Error pattern for recognition
218#[derive(Debug, Clone)]
219pub struct ErrorPattern {
220    /// Pattern name
221    pub name: String,
222
223    /// Pattern description
224    pub description: String,
225
226    /// Matching criteria
227    pub criteria: Vec<MatchCriterion>,
228
229    /// Common causes
230    pub common_causes: Vec<String>,
231
232    /// Suggested solutions
233    pub solutions: Vec<RecoverySuggestion>,
234
235    /// Frequency of occurrence
236    pub frequency: u64,
237}
238
239/// Criteria for matching error patterns
240#[derive(Debug, Clone)]
241pub enum MatchCriterion {
242    /// Message contains text
243    MessageContains(String),
244
245    /// Error category matches
246    CategoryEquals(ErrorCategory),
247
248    /// Source location matches pattern
249    LocationMatches(String),
250
251    /// Stack trace contains function
252    StackContains(String),
253
254    /// Custom matcher
255    Custom(fn(&DiagnosticError) -> bool),
256}
257
258/// Recovery suggestion
259#[derive(Debug, Clone)]
260pub struct RecoverySuggestion {
261    /// Suggestion type
262    pub suggestion_type: SuggestionType,
263
264    /// Suggestion message
265    pub message: String,
266
267    /// Detailed explanation
268    pub explanation: Option<String>,
269
270    /// Code example (if applicable)
271    pub code_example: Option<String>,
272
273    /// Link to documentation
274    pub doc_link: Option<String>,
275
276    /// Confidence level (0.0 - 1.0)
277    pub confidence: f32,
278
279    /// Automatic fix available
280    pub auto_fix: Option<AutoFix>,
281}
282
283/// Types of suggestions
284#[derive(Debug, Clone)]
285pub enum SuggestionType {
286    /// Quick fix
287    QuickFix,
288
289    /// Code change
290    CodeChange,
291
292    /// Configuration change
293    ConfigChange,
294
295    /// Environment setup
296    EnvironmentSetup,
297
298    /// Documentation reference
299    Documentation,
300
301    /// Workaround
302    Workaround,
303
304    /// Investigation required
305    Investigation,
306}
307
308/// Automatic fix information
309#[derive(Debug, Clone)]
310pub struct AutoFix {
311    /// Fix description
312    pub description: String,
313
314    /// Fix function
315    pub fix_fn: fn(&DiagnosticError) -> JitResult<()>,
316
317    /// Side effects
318    pub side_effects: Vec<String>,
319
320    /// Requires user confirmation
321    pub requires_confirmation: bool,
322}
323
324/// Diagnostics configuration
325#[derive(Debug, Clone)]
326pub struct DiagnosticsConfig {
327    /// Enable error diagnostics
328    pub enabled: bool,
329
330    /// Maximum error history size
331    pub max_history_size: usize,
332
333    /// Enable stack trace collection
334    pub collect_stack_traces: bool,
335
336    /// Enable source snippet extraction
337    pub extract_source_snippets: bool,
338
339    /// Error reporting level
340    pub reporting_level: ErrorSeverity,
341
342    /// Enable pattern matching
343    pub enable_pattern_matching: bool,
344
345    /// Enable recovery suggestions
346    pub enable_suggestions: bool,
347
348    /// Maximum suggestions per error
349    pub max_suggestions: usize,
350
351    /// Color output for terminal
352    pub color_output: bool,
353
354    /// Verbose output
355    pub verbose: bool,
356}
357
358/// Diagnostics statistics
359#[derive(Debug, Clone, Default)]
360pub struct DiagnosticsStats {
361    /// Total errors recorded
362    pub total_errors: u64,
363
364    /// Errors by category
365    pub errors_by_category: HashMap<ErrorCategory, u64>,
366
367    /// Errors by severity
368    pub errors_by_severity: HashMap<ErrorSeverity, u64>,
369
370    /// Pattern matches
371    pub pattern_matches: u64,
372
373    /// Suggestions provided
374    pub suggestions_provided: u64,
375
376    /// Auto-fixes applied
377    pub auto_fixes_applied: u64,
378}
379
380/// Error formatter for different output formats
381pub struct ErrorFormatter {
382    /// Formatting configuration
383    config: FormatterConfig,
384}
385
386/// Formatter configuration
387#[derive(Debug, Clone)]
388pub struct FormatterConfig {
389    /// Include source snippets
390    pub include_source: bool,
391
392    /// Include stack traces
393    pub include_stack_trace: bool,
394
395    /// Include suggestions
396    pub include_suggestions: bool,
397
398    /// Use colors
399    pub use_colors: bool,
400
401    /// Maximum line length
402    pub max_line_length: usize,
403
404    /// Indentation size
405    pub indent_size: usize,
406}
407
408impl Default for DiagnosticsConfig {
409    fn default() -> Self {
410        Self {
411            enabled: true,
412            max_history_size: 1000,
413            collect_stack_traces: true,
414            extract_source_snippets: true,
415            reporting_level: ErrorSeverity::Warning,
416            enable_pattern_matching: true,
417            enable_suggestions: true,
418            max_suggestions: 5,
419            color_output: true,
420            verbose: false,
421        }
422    }
423}
424
425impl Default for FormatterConfig {
426    fn default() -> Self {
427        Self {
428            include_source: true,
429            include_stack_trace: true,
430            include_suggestions: true,
431            use_colors: true,
432            max_line_length: 120,
433            indent_size: 2,
434        }
435    }
436}
437
438impl ErrorDiagnosticsManager {
439    /// Create a new error diagnostics manager
440    pub fn new(config: DiagnosticsConfig) -> Self {
441        let mut manager = Self {
442            config,
443            error_history: Vec::new(),
444            error_patterns: HashMap::new(),
445            recovery_suggestions: HashMap::new(),
446            context_stack: Vec::new(),
447            stats: DiagnosticsStats::default(),
448        };
449
450        manager.initialize_default_patterns();
451        manager.initialize_default_suggestions();
452        manager
453    }
454
455    /// Create a new manager with default configuration
456    pub fn with_defaults() -> Self {
457        Self::new(DiagnosticsConfig::default())
458    }
459
460    /// Record an error with diagnostics
461    pub fn record_error(&mut self, error: JitError) -> DiagnosticError {
462        let mut diagnostic_error = self.create_diagnostic_error(error);
463
464        // Update statistics
465        self.stats.total_errors += 1;
466        *self
467            .stats
468            .errors_by_category
469            .entry(diagnostic_error.category.clone())
470            .or_insert(0) += 1;
471        *self
472            .stats
473            .errors_by_severity
474            .entry(diagnostic_error.severity.clone())
475            .or_insert(0) += 1;
476
477        // Try to match error patterns
478        if self.config.enable_pattern_matching {
479            self.match_error_patterns(&diagnostic_error);
480        }
481
482        // Add recovery suggestions
483        if self.config.enable_suggestions {
484            self.add_recovery_suggestions(&mut diagnostic_error);
485        }
486
487        // Store in history (clone for history, but we need to return the original)
488        let diagnostic_error_copy = DiagnosticError {
489            id: diagnostic_error.id.clone(),
490            timestamp: diagnostic_error.timestamp,
491            category: diagnostic_error.category.clone(),
492            severity: diagnostic_error.severity.clone(),
493            message: diagnostic_error.message.clone(),
494            source_location: diagnostic_error.source_location.clone(),
495            stack_trace: diagnostic_error.stack_trace.clone(),
496            context: diagnostic_error.context.clone(),
497            related_errors: diagnostic_error.related_errors.clone(),
498            suggestions: diagnostic_error.suggestions.clone(),
499            metadata: diagnostic_error.metadata.clone(),
500            underlying_error: None, // Don't store the underlying error in history to avoid clone issues
501        };
502
503        if self.error_history.len() >= self.config.max_history_size {
504            self.error_history.remove(0);
505        }
506        self.error_history.push(diagnostic_error_copy);
507
508        diagnostic_error
509    }
510
511    /// Create a diagnostic error from a JIT error
512    fn create_diagnostic_error(&self, error: JitError) -> DiagnosticError {
513        let error_id = format!("err_{}", self.stats.total_errors);
514        let category = self.categorize_error(&error);
515        let severity = self.determine_severity(&error);
516        let message = error.to_string();
517
518        let context = self
519            .context_stack
520            .last()
521            .cloned()
522            .unwrap_or_else(|| DiagnosticContext {
523                operation: "unknown".to_string(),
524                input: "unknown".to_string(),
525                expected: None,
526                actual: None,
527                environment: self.get_environment_info(),
528                data: HashMap::new(),
529            });
530
531        DiagnosticError {
532            id: error_id,
533            timestamp: Instant::now(),
534            category,
535            severity,
536            message,
537            source_location: None,
538            stack_trace: self.collect_stack_trace(),
539            context,
540            related_errors: Vec::new(),
541            suggestions: Vec::new(),
542            metadata: HashMap::new(),
543            underlying_error: Some(Box::new(error)),
544        }
545    }
546
547    /// Categorize an error
548    fn categorize_error(&self, error: &JitError) -> ErrorCategory {
549        match error {
550            JitError::GraphError(_) => ErrorCategory::GraphConstruction,
551            JitError::OptimizationError(_) => ErrorCategory::Optimization,
552            JitError::CodeGenError(_) => ErrorCategory::CodeGeneration,
553            JitError::RuntimeError(_) => ErrorCategory::Runtime,
554            JitError::UnsupportedOp(_) => ErrorCategory::UserInput,
555            JitError::CompilationError(_) => ErrorCategory::CodeGeneration,
556            JitError::AnalysisError(_) => ErrorCategory::TypeInference,
557            JitError::BackendError(_) => ErrorCategory::External,
558            JitError::FusionError(_) => ErrorCategory::Optimization,
559            JitError::AbstractInterpretationError(_) => ErrorCategory::TypeInference,
560        }
561    }
562
563    /// Determine error severity
564    fn determine_severity(&self, error: &JitError) -> ErrorSeverity {
565        match error {
566            JitError::GraphError(_) => ErrorSeverity::Error,
567            JitError::OptimizationError(_) => ErrorSeverity::Warning,
568            JitError::CodeGenError(_) => ErrorSeverity::Error,
569            JitError::RuntimeError(_) => ErrorSeverity::Error,
570            JitError::UnsupportedOp(_) => ErrorSeverity::Error,
571            JitError::CompilationError(_) => ErrorSeverity::Error,
572            JitError::AnalysisError(_) => ErrorSeverity::Warning,
573            JitError::BackendError(_) => ErrorSeverity::Fatal,
574            JitError::FusionError(_) => ErrorSeverity::Warning,
575            JitError::AbstractInterpretationError(_) => ErrorSeverity::Warning,
576        }
577    }
578
579    /// Collect stack trace
580    fn collect_stack_trace(&self) -> Vec<StackFrame> {
581        if !self.config.collect_stack_traces {
582            return Vec::new();
583        }
584
585        // Use std::backtrace if available, otherwise provide basic frame
586        #[cfg(feature = "std_backtrace")]
587        {
588            use std::backtrace::{Backtrace, BacktraceStatus};
589            let bt = Backtrace::capture();
590            if bt.status() == BacktraceStatus::Captured {
591                // Parse backtrace frames
592                let bt_str = format!("{:?}", bt);
593                return self.parse_backtrace_string(&bt_str);
594            }
595        }
596
597        // Fallback: collect limited stack trace using thread info
598        let mut frames = Vec::new();
599
600        // Add current thread information
601        let thread = std::thread::current();
602        frames.push(StackFrame {
603            function: thread.name().unwrap_or("unknown").to_string(),
604            file: None,
605            line: None,
606            address: None,
607            module: Some("torsh_jit".to_string()),
608        });
609
610        frames
611    }
612
613    /// Parse backtrace string into stack frames
614    #[cfg(feature = "std_backtrace")]
615    fn parse_backtrace_string(&self, backtrace: &str) -> Vec<StackFrame> {
616        let mut frames = Vec::new();
617        for line in backtrace.lines().take(20) {
618            // Simple parsing - can be enhanced
619            if let Some(function) = line.split("::").last() {
620                frames.push(StackFrame {
621                    function: function.trim().to_string(),
622                    file: None,
623                    line: None,
624                    address: None,
625                    module: Some("torsh_jit".to_string()),
626                });
627            }
628        }
629        frames
630    }
631
632    /// Get environment information
633    fn get_environment_info(&self) -> EnvironmentInfo {
634        EnvironmentInfo {
635            rust_version: self.get_rust_version(),
636            torsh_version: env!("CARGO_PKG_VERSION").to_string(),
637            target_arch: std::env::consts::ARCH.to_string(),
638            target_os: std::env::consts::OS.to_string(),
639            available_memory: self.get_available_memory(),
640            cpu_info: self.get_cpu_info(),
641            gpu_info: self.get_gpu_info(),
642        }
643    }
644
645    /// Get Rust version
646    fn get_rust_version(&self) -> String {
647        // Try to get from rustc --version
648        std::env::var("RUSTC_VERSION")
649            .unwrap_or_else(|_| env!("CARGO_PKG_RUST_VERSION").to_string())
650    }
651
652    /// Get available memory information in bytes
653    fn get_available_memory(&self) -> Option<u64> {
654        #[cfg(target_os = "linux")]
655        {
656            if let Ok(contents) = std::fs::read_to_string("/proc/meminfo") {
657                for line in contents.lines() {
658                    if line.starts_with("MemTotal:") {
659                        if let Some(kb_str) = line.split_whitespace().nth(1) {
660                            if let Ok(kb) = kb_str.parse::<u64>() {
661                                return Some(kb * 1024); // Convert KB to bytes
662                            }
663                        }
664                    }
665                }
666            }
667        }
668
669        #[cfg(target_os = "macos")]
670        {
671            use std::process::Command;
672            if let Ok(output) = Command::new("sysctl").arg("hw.memsize").output() {
673                if let Ok(text) = String::from_utf8(output.stdout) {
674                    if let Some(size) = text.split(':').nth(1) {
675                        if let Ok(bytes) = size.trim().parse::<u64>() {
676                            return Some(bytes);
677                        }
678                    }
679                }
680            }
681        }
682
683        None
684    }
685
686    /// Get CPU information
687    fn get_cpu_info(&self) -> Option<String> {
688        #[cfg(target_os = "linux")]
689        {
690            if let Ok(contents) = std::fs::read_to_string("/proc/cpuinfo") {
691                for line in contents.lines() {
692                    if line.starts_with("model name") {
693                        if let Some(name) = line.split(':').nth(1) {
694                            return Some(name.trim().to_string());
695                        }
696                    }
697                }
698            }
699        }
700
701        #[cfg(target_os = "macos")]
702        {
703            use std::process::Command;
704            if let Ok(output) = Command::new("sysctl")
705                .arg("-n")
706                .arg("machdep.cpu.brand_string")
707                .output()
708            {
709                if let Ok(cpu) = String::from_utf8(output.stdout) {
710                    return Some(cpu.trim().to_string());
711                }
712            }
713        }
714
715        Some(format!("{} core(s)", num_cpus::get()))
716    }
717
718    /// Get GPU information
719    fn get_gpu_info(&self) -> Option<String> {
720        #[cfg(feature = "gpu")]
721        {
722            // Would integrate with torsh-backend-cuda for actual GPU info
723            // For now, return basic placeholder
724            Some("GPU support enabled".to_string())
725        }
726
727        #[cfg(not(feature = "gpu"))]
728        {
729            None
730        }
731    }
732
733    /// Match error patterns
734    fn match_error_patterns(&mut self, error: &DiagnosticError) {
735        let mut matched_patterns = Vec::new();
736        for (pattern_name, pattern) in &self.error_patterns {
737            if self.matches_pattern(error, pattern) {
738                self.stats.pattern_matches += 1;
739                matched_patterns.push((pattern_name.clone(), pattern.clone()));
740            }
741        }
742
743        // Apply matched patterns (need to drop the immutable borrow first)
744        // This is a simplified version - in production we'd integrate this better
745        for (_pattern_name, _pattern) in matched_patterns {
746            // Pattern handling would be applied here
747        }
748    }
749
750    /// Apply pattern-specific error handling
751    fn apply_pattern_handling(
752        &mut self,
753        error: &mut DiagnosticError,
754        pattern_name: &str,
755        pattern: &ErrorPattern,
756    ) {
757        // Add pattern-specific suggestions (without checking for duplicates)
758        for suggestion in &pattern.solutions {
759            error.suggestions.push(suggestion.clone());
760        }
761
762        // Add common causes as related information
763        for cause in &pattern.common_causes {
764            error
765                .related_errors
766                .push(format!("Common cause: {}", cause));
767        }
768
769        // Add context information
770        if let Some(ref ctx) = self.context_stack.last() {
771            error.related_errors.push(format!(
772                "Pattern '{}' matched in context: {}",
773                pattern_name, ctx.operation
774            ));
775        }
776
777        // Record pattern match for statistics
778        self.stats.suggestions_provided += pattern.solutions.len() as u64;
779
780        // Adjust error severity if pattern is frequently occurring
781        if pattern.frequency > 100 {
782            // Downgrade frequently occurring errors to warnings
783            if error.severity == ErrorSeverity::Error {
784                error.severity = ErrorSeverity::Warning;
785            }
786        }
787    }
788
789    /// Check if error matches a pattern
790    fn matches_pattern(&self, error: &DiagnosticError, pattern: &ErrorPattern) -> bool {
791        for criterion in &pattern.criteria {
792            match criterion {
793                MatchCriterion::MessageContains(text) => {
794                    if !error.message.contains(text) {
795                        return false;
796                    }
797                }
798                MatchCriterion::CategoryEquals(category) => {
799                    if error.category != *category {
800                        return false;
801                    }
802                }
803                MatchCriterion::LocationMatches(pattern) => {
804                    if let Some(ref location) = error.source_location {
805                        let location_str =
806                            format!("{}:{}:{}", location.file, location.line, location.column);
807                        if !location_str.contains(pattern) {
808                            return false;
809                        }
810                    } else {
811                        return false;
812                    }
813                }
814                MatchCriterion::StackContains(function) => {
815                    if !error
816                        .stack_trace
817                        .iter()
818                        .any(|frame| frame.function.contains(function))
819                    {
820                        return false;
821                    }
822                }
823                MatchCriterion::Custom(matcher) => {
824                    if !matcher(error) {
825                        return false;
826                    }
827                }
828            }
829        }
830        true
831    }
832
833    /// Add recovery suggestions to an error
834    fn add_recovery_suggestions(&mut self, error: &mut DiagnosticError) {
835        if let Some(suggestions) = self.recovery_suggestions.get(&error.category) {
836            for suggestion in suggestions.iter().take(self.config.max_suggestions) {
837                error.suggestions.push(suggestion.clone());
838                self.stats.suggestions_provided += 1;
839            }
840        }
841    }
842
843    /// Initialize default error patterns
844    fn initialize_default_patterns(&mut self) {
845        // Type mismatch pattern
846        let type_mismatch = ErrorPattern {
847            name: "type_mismatch".to_string(),
848            description: "Type mismatch in operation".to_string(),
849            criteria: vec![
850                MatchCriterion::MessageContains("type".to_string()),
851                MatchCriterion::CategoryEquals(ErrorCategory::TypeInference),
852            ],
853            common_causes: vec![
854                "Incorrect input types".to_string(),
855                "Missing type annotations".to_string(),
856            ],
857            solutions: vec![],
858            frequency: 0,
859        };
860
861        self.error_patterns
862            .insert("type_mismatch".to_string(), type_mismatch);
863
864        // Shape mismatch pattern
865        let shape_mismatch = ErrorPattern {
866            name: "shape_mismatch".to_string(),
867            description: "Shape mismatch in tensor operation".to_string(),
868            criteria: vec![
869                MatchCriterion::MessageContains("shape".to_string()),
870                MatchCriterion::CategoryEquals(ErrorCategory::ShapeInference),
871            ],
872            common_causes: vec![
873                "Incompatible tensor shapes".to_string(),
874                "Missing shape information".to_string(),
875            ],
876            solutions: vec![],
877            frequency: 0,
878        };
879
880        self.error_patterns
881            .insert("shape_mismatch".to_string(), shape_mismatch);
882    }
883
884    /// Initialize default recovery suggestions
885    fn initialize_default_suggestions(&mut self) {
886        // Type inference suggestions
887        let type_suggestions = vec![RecoverySuggestion {
888            suggestion_type: SuggestionType::CodeChange,
889            message: "Check input types and add explicit type annotations".to_string(),
890            explanation: Some(
891                "Type inference failed. Consider adding explicit type information.".to_string(),
892            ),
893            code_example: Some("tensor.cast(DType::F32)".to_string()),
894            doc_link: Some("https://docs.rs/torsh/latest/torsh/".to_string()),
895            confidence: 0.8,
896            auto_fix: None,
897        }];
898
899        self.recovery_suggestions
900            .insert(ErrorCategory::TypeInference, type_suggestions);
901
902        // Shape inference suggestions
903        let shape_suggestions = vec![
904            RecoverySuggestion {
905                suggestion_type: SuggestionType::CodeChange,
906                message: "Verify tensor shapes are compatible for the operation".to_string(),
907                explanation: Some("Shape inference failed. Check that tensor dimensions match operation requirements.".to_string()),
908                code_example: Some("tensor.reshape(&[batch_size, channels, height, width])".to_string()),
909                doc_link: Some("https://docs.rs/torsh/latest/torsh/".to_string()),
910                confidence: 0.9,
911                auto_fix: None,
912            },
913        ];
914
915        self.recovery_suggestions
916            .insert(ErrorCategory::ShapeInference, shape_suggestions);
917    }
918
919    /// Push diagnostic context
920    pub fn push_context(&mut self, context: DiagnosticContext) {
921        self.context_stack.push(context);
922    }
923
924    /// Pop diagnostic context
925    pub fn pop_context(&mut self) -> Option<DiagnosticContext> {
926        self.context_stack.pop()
927    }
928
929    /// Get error history
930    pub fn get_error_history(&self) -> &[DiagnosticError] {
931        &self.error_history
932    }
933
934    /// Get statistics
935    pub fn get_stats(&self) -> &DiagnosticsStats {
936        &self.stats
937    }
938
939    /// Format error for display
940    pub fn format_error(&self, error: &DiagnosticError, format_config: &FormatterConfig) -> String {
941        let formatter = ErrorFormatter::new(format_config.clone());
942        formatter.format(error)
943    }
944
945    /// Get similar errors from history
946    pub fn get_similar_errors(&self, error: &DiagnosticError) -> Vec<&DiagnosticError> {
947        self.error_history
948            .iter()
949            .filter(|e| e.category == error.category && e.severity == error.severity)
950            .collect()
951    }
952
953    /// Export diagnostics data
954    pub fn export_diagnostics(&self, output_path: &str) -> JitResult<()> {
955        let diagnostics_data = format!(
956            r#"{{"total_errors": {}, "errors_by_category": {:?}, "patterns": {}}}"#,
957            self.stats.total_errors,
958            self.stats.errors_by_category,
959            self.error_patterns.len()
960        );
961
962        std::fs::write(output_path, diagnostics_data)
963            .map_err(|e| JitError::RuntimeError(format!("Failed to export diagnostics: {}", e)))?;
964
965        Ok(())
966    }
967}
968
969impl ErrorFormatter {
970    /// Create a new error formatter
971    pub fn new(config: FormatterConfig) -> Self {
972        Self { config }
973    }
974
975    /// Format a diagnostic error
976    pub fn format(&self, error: &DiagnosticError) -> String {
977        let mut output = String::new();
978
979        // Header
980        output.push_str(&format!(
981            "{}[{}] {}: {}\n",
982            self.color_for_severity(&error.severity),
983            error.severity.as_str(),
984            error.category.as_str(),
985            error.message
986        ));
987
988        // Source location
989        if let Some(location) = &error.source_location {
990            output.push_str(&format!(
991                "  --> {}:{}:{}\n",
992                location.file, location.line, location.column
993            ));
994
995            if self.config.include_source {
996                if let Some(snippet) = &location.snippet {
997                    output.push_str(&format!("   |\n   | {}\n   |\n", snippet));
998                }
999            }
1000        }
1001
1002        // Context
1003        output.push_str(&format!(
1004            "  Context: {} ({})\n",
1005            error.context.operation, error.context.input
1006        ));
1007
1008        // Stack trace
1009        if self.config.include_stack_trace && !error.stack_trace.is_empty() {
1010            output.push_str("  Stack trace:\n");
1011            for frame in &error.stack_trace {
1012                output.push_str(&format!(
1013                    "    at {} ({}:{})\n",
1014                    frame.function,
1015                    frame.file.as_ref().unwrap_or(&"unknown".to_string()),
1016                    frame.line.unwrap_or(0)
1017                ));
1018            }
1019        }
1020
1021        // Suggestions
1022        if self.config.include_suggestions && !error.suggestions.is_empty() {
1023            output.push_str("  Suggestions:\n");
1024            for suggestion in &error.suggestions {
1025                output.push_str(&format!("    - {}\n", suggestion.message));
1026                if let Some(explanation) = &suggestion.explanation {
1027                    output.push_str(&format!("      {}\n", explanation));
1028                }
1029            }
1030        }
1031
1032        output.push_str(&self.reset_color());
1033        output
1034    }
1035
1036    /// Get color for severity level
1037    fn color_for_severity(&self, severity: &ErrorSeverity) -> &str {
1038        if !self.config.use_colors {
1039            return "";
1040        }
1041
1042        match severity {
1043            ErrorSeverity::Info => "\x1b[36m",    // Cyan
1044            ErrorSeverity::Warning => "\x1b[33m", // Yellow
1045            ErrorSeverity::Error => "\x1b[31m",   // Red
1046            ErrorSeverity::Fatal => "\x1b[35m",   // Magenta
1047            ErrorSeverity::Ice => "\x1b[41m",     // Red background
1048        }
1049    }
1050
1051    /// Reset color
1052    fn reset_color(&self) -> &str {
1053        if self.config.use_colors {
1054            "\x1b[0m"
1055        } else {
1056            ""
1057        }
1058    }
1059}
1060
1061impl ErrorSeverity {
1062    /// Get string representation
1063    pub fn as_str(&self) -> &str {
1064        match self {
1065            ErrorSeverity::Info => "INFO",
1066            ErrorSeverity::Warning => "WARNING",
1067            ErrorSeverity::Error => "ERROR",
1068            ErrorSeverity::Fatal => "FATAL",
1069            ErrorSeverity::Ice => "ICE",
1070        }
1071    }
1072}
1073
1074impl ErrorCategory {
1075    /// Get string representation
1076    pub fn as_str(&self) -> &str {
1077        match self {
1078            ErrorCategory::GraphConstruction => "GRAPH",
1079            ErrorCategory::TypeInference => "TYPE",
1080            ErrorCategory::ShapeInference => "SHAPE",
1081            ErrorCategory::Optimization => "OPT",
1082            ErrorCategory::CodeGeneration => "CODEGEN",
1083            ErrorCategory::Runtime => "RUNTIME",
1084            ErrorCategory::Memory => "MEMORY",
1085            ErrorCategory::Resource => "RESOURCE",
1086            ErrorCategory::UserInput => "INPUT",
1087            ErrorCategory::Internal => "INTERNAL",
1088            ErrorCategory::External => "EXTERNAL",
1089        }
1090    }
1091}
1092
1093impl fmt::Display for DiagnosticError {
1094    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1095        write!(
1096            f,
1097            "[{}] {}: {}",
1098            self.severity.as_str(),
1099            self.category.as_str(),
1100            self.message
1101        )
1102    }
1103}
1104
1105#[cfg(test)]
1106mod tests {
1107    use super::*;
1108
1109    #[test]
1110    fn test_diagnostics_manager_creation() {
1111        let manager = ErrorDiagnosticsManager::with_defaults();
1112        assert!(manager.config.enabled);
1113        assert_eq!(manager.config.max_history_size, 1000);
1114    }
1115
1116    #[test]
1117    fn test_error_recording() {
1118        let mut manager = ErrorDiagnosticsManager::with_defaults();
1119        let error = JitError::RuntimeError("Test error".to_string());
1120
1121        let diagnostic_error = manager.record_error(error);
1122        assert_eq!(diagnostic_error.category, ErrorCategory::Runtime);
1123        assert_eq!(diagnostic_error.severity, ErrorSeverity::Error);
1124        assert!(diagnostic_error.message.contains("Test error"));
1125    }
1126
1127    #[test]
1128    fn test_error_categorization() {
1129        let manager = ErrorDiagnosticsManager::with_defaults();
1130
1131        let graph_error = JitError::GraphError("Graph error".to_string());
1132        assert_eq!(
1133            manager.categorize_error(&graph_error),
1134            ErrorCategory::GraphConstruction
1135        );
1136
1137        let runtime_error = JitError::RuntimeError("Runtime error".to_string());
1138        assert_eq!(
1139            manager.categorize_error(&runtime_error),
1140            ErrorCategory::Runtime
1141        );
1142    }
1143
1144    #[test]
1145    fn test_error_formatting() {
1146        let mut manager = ErrorDiagnosticsManager::with_defaults();
1147        let error = JitError::RuntimeError("Test error".to_string());
1148        let diagnostic_error = manager.record_error(error);
1149
1150        let formatter_config = FormatterConfig::default();
1151        let formatted = manager.format_error(&diagnostic_error, &formatter_config);
1152
1153        assert!(formatted.contains("ERROR"));
1154        assert!(formatted.contains("RUNTIME"));
1155        assert!(formatted.contains("Test error"));
1156    }
1157
1158    #[test]
1159    fn test_context_stack() {
1160        let mut manager = ErrorDiagnosticsManager::with_defaults();
1161
1162        let context = DiagnosticContext {
1163            operation: "test_operation".to_string(),
1164            input: "test_input".to_string(),
1165            expected: None,
1166            actual: None,
1167            environment: manager.get_environment_info(),
1168            data: HashMap::new(),
1169        };
1170
1171        manager.push_context(context.clone());
1172        assert_eq!(manager.context_stack.len(), 1);
1173
1174        let popped = manager.pop_context().unwrap();
1175        assert_eq!(popped.operation, "test_operation");
1176        assert_eq!(manager.context_stack.len(), 0);
1177    }
1178}