rusty_cpp/diagnostics/
mod.rs

1use colored::*;
2use std::fmt;
3
4#[derive(Debug, Clone)]
5pub struct BorrowCheckDiagnostic {
6    pub severity: Severity,
7    pub message: String,
8    pub location: Location,
9    pub help: Option<String>,
10    pub notes: Vec<String>,
11}
12
13#[derive(Debug, Clone)]
14#[allow(dead_code)]
15pub enum Severity {
16    Error,
17    Warning,
18    Note,
19}
20
21#[derive(Debug, Clone)]
22pub struct Location {
23    pub file: String,
24    pub line: u32,
25    pub column: u32,
26    #[allow(dead_code)]
27    pub span: Option<(usize, usize)>,
28}
29
30impl fmt::Display for BorrowCheckDiagnostic {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        let severity_str = match self.severity {
33            Severity::Error => "error".red().bold(),
34            Severity::Warning => "warning".yellow().bold(),
35            Severity::Note => "note".blue().bold(),
36        };
37        
38        writeln!(
39            f,
40            "{}: {}",
41            severity_str,
42            self.message.bold()
43        )?;
44        
45        writeln!(
46            f,
47            "  {} {}:{}:{}",
48            "-->".blue(),
49            self.location.file,
50            self.location.line,
51            self.location.column
52        )?;
53        
54        if let Some(ref help) = self.help {
55            writeln!(f, "{}: {}", "help".green().bold(), help)?;
56        }
57        
58        for note in &self.notes {
59            writeln!(f, "{}: {}", "note".blue(), note)?;
60        }
61        
62        Ok(())
63    }
64}
65
66#[allow(dead_code)]
67pub fn format_use_after_move(var_name: &str, location: Location) -> BorrowCheckDiagnostic {
68    BorrowCheckDiagnostic {
69        severity: Severity::Error,
70        message: format!("use of moved value: `{}`", var_name),
71        location,
72        help: Some(format!(
73            "consider using `std::move` explicitly or creating a copy"
74        )),
75        notes: vec![
76            format!("value `{}` was moved previously", var_name),
77            "once a value is moved, it cannot be used again".to_string(),
78        ],
79    }
80}
81
82#[allow(dead_code)]
83pub fn format_double_borrow(var_name: &str, location: Location) -> BorrowCheckDiagnostic {
84    BorrowCheckDiagnostic {
85        severity: Severity::Error,
86        message: format!("cannot borrow `{}` as mutable more than once", var_name),
87        location,
88        help: Some("consider using a shared reference instead".to_string()),
89        notes: vec![
90            "only one mutable borrow is allowed at a time".to_string(),
91        ],
92    }
93}
94
95#[allow(dead_code)]
96pub fn format_lifetime_error(message: String, location: Location) -> BorrowCheckDiagnostic {
97    BorrowCheckDiagnostic {
98        severity: Severity::Error,
99        message,
100        location,
101        help: Some("consider adjusting the lifetime annotations".to_string()),
102        notes: vec![],
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn test_diagnostic_creation() {
112        let diag = BorrowCheckDiagnostic {
113            severity: Severity::Error,
114            message: "Test error".to_string(),
115            location: Location {
116                file: "test.cpp".to_string(),
117                line: 10,
118                column: 5,
119                span: Some((100, 105)),
120            },
121            help: Some("Try this instead".to_string()),
122            notes: vec!["Note 1".to_string()],
123        };
124        
125        assert!(matches!(diag.severity, Severity::Error));
126        assert_eq!(diag.message, "Test error");
127        assert_eq!(diag.location.line, 10);
128        assert_eq!(diag.help, Some("Try this instead".to_string()));
129        assert_eq!(diag.notes.len(), 1);
130    }
131
132    #[test]
133    fn test_format_use_after_move() {
134        let location = Location {
135            file: "test.cpp".to_string(),
136            line: 42,
137            column: 8,
138            span: None,
139        };
140        
141        let diag = format_use_after_move("ptr", location);
142        
143        assert!(matches!(diag.severity, Severity::Error));
144        assert!(diag.message.contains("ptr"));
145        assert!(diag.message.contains("moved"));
146        assert!(diag.help.is_some());
147        assert!(!diag.notes.is_empty());
148    }
149
150    #[test]
151    fn test_format_double_borrow() {
152        let location = Location {
153            file: "test.cpp".to_string(),
154            line: 15,
155            column: 3,
156            span: None,
157        };
158        
159        let diag = format_double_borrow("value", location);
160        
161        assert!(matches!(diag.severity, Severity::Error));
162        assert!(diag.message.contains("value"));
163        assert!(diag.message.contains("mutable"));
164        assert!(diag.help.is_some());
165    }
166
167    #[test]
168    fn test_format_lifetime_error() {
169        let location = Location {
170            file: "test.cpp".to_string(),
171            line: 20,
172            column: 10,
173            span: Some((200, 210)),
174        };
175        
176        let diag = format_lifetime_error(
177            "Reference outlives its source".to_string(),
178            location
179        );
180        
181        assert!(matches!(diag.severity, Severity::Error));
182        assert_eq!(diag.message, "Reference outlives its source");
183        assert!(diag.help.is_some());
184    }
185
186    #[test]
187    fn test_diagnostic_display() {
188        let diag = BorrowCheckDiagnostic {
189            severity: Severity::Error,
190            message: "Test error message".to_string(),
191            location: Location {
192                file: "example.cpp".to_string(),
193                line: 5,
194                column: 10,
195                span: None,
196            },
197            help: None,
198            notes: vec![],
199        };
200        
201        let output = format!("{}", diag);
202        assert!(output.contains("error"));
203        assert!(output.contains("Test error message"));
204        assert!(output.contains("example.cpp:5:10"));
205    }
206}