python_ast/
result.rs

1
2use pyo3::PyErr;
3use thiserror::Error as E;
4
5use crate::{BinOp, BoolOp, Compare, Expr, ExprType, StatementType, UnaryOp, PositionInfo};
6
7/// Location information for error reporting
8#[derive(Debug, Clone, PartialEq)]
9pub struct SourceLocation {
10    pub filename: String,
11    pub line: Option<usize>,
12    pub column: Option<usize>,
13    pub end_line: Option<usize>,
14    pub end_column: Option<usize>,
15}
16
17impl SourceLocation {
18    pub fn new(filename: impl Into<String>) -> Self {
19        Self {
20            filename: filename.into(),
21            line: None,
22            column: None,
23            end_line: None,
24            end_column: None,
25        }
26    }
27
28    pub fn with_position(
29        filename: impl Into<String>,
30        line: Option<usize>,
31        column: Option<usize>,
32    ) -> Self {
33        Self {
34            filename: filename.into(),
35            line,
36            column,
37            end_line: None,
38            end_column: None,
39        }
40    }
41
42    pub fn with_span(
43        filename: impl Into<String>,
44        line: Option<usize>,
45        column: Option<usize>,
46        end_line: Option<usize>,
47        end_column: Option<usize>,
48    ) -> Self {
49        Self {
50            filename: filename.into(),
51            line,
52            column,
53            end_line,
54            end_column,
55        }
56    }
57
58    /// Create a SourceLocation from an AST node that implements PositionInfo
59    pub fn from_node(filename: impl Into<String>, node: &dyn PositionInfo) -> Self {
60        let (line, column, end_line, end_column) = node.position_info();
61        Self {
62            filename: filename.into(),
63            line,
64            column,
65            end_line,
66            end_column,
67        }
68    }
69}
70
71impl std::fmt::Display for SourceLocation {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        match (self.line, self.column) {
74            (Some(line), Some(col)) => {
75                if let (Some(end_line), Some(end_col)) = (self.end_line, self.end_column) {
76                    if line == end_line {
77                        write!(f, "{}:{}:{}-{}", self.filename, line, col, end_col)
78                    } else {
79                        write!(f, "{}:{}:{}-{}:{}", self.filename, line, col, end_line, end_col)
80                    }
81                } else {
82                    write!(f, "{}:{}:{}", self.filename, line, col)
83                }
84            }
85            (Some(line), None) => write!(f, "{}:{}", self.filename, line),
86            _ => write!(f, "{}", self.filename),
87        }
88    }
89}
90
91#[derive(E, Debug)]
92pub enum Error {
93    #[error("Parsing error at {location}: {message}\nHelp: {help}")]
94    ParseError {
95        location: SourceLocation,
96        message: String,
97        help: String,
98    },
99
100    #[error("Code generation error at {location}: {message}\nHelp: {help}")]
101    CodeGenError {
102        location: SourceLocation,
103        message: String,
104        help: String,
105    },
106
107    #[error("Unsupported feature at {location}: {feature} is not yet implemented\nHelp: {help}")]
108    UnsupportedFeature {
109        location: SourceLocation,
110        feature: String,
111        help: String,
112    },
113
114    #[error("Type error at {location}: {message}\nExpected: {expected}\nFound: {found}\nHelp: {help}")]
115    TypeError {
116        location: SourceLocation,
117        message: String,
118        expected: String,
119        found: String,
120        help: String,
121    },
122
123    #[error("Invalid syntax at {location}: {message}\nHelp: {help}")]
124    SyntaxError {
125        location: SourceLocation,
126        message: String,
127        help: String,
128    },
129
130    // Legacy error types for backward compatibility
131    #[error("BinOp type not yet implemented: {:?}", .0)]
132    BinOpNotYetImplemented(BinOp),
133
134    #[error("BoolOp type not yet implemented: {:?}", .0)]
135    BoolOpNotYetImplemented(BoolOp),
136
137    #[error("Compare type not yet implemented: {:?}", .0)]
138    CompareNotYetImplemented(Compare),
139
140    #[error("Expr type not yet implemented: {:?}", .0)]
141    ExprNotYetImplemented(Expr),
142    #[error("ExprType type not yet implemented: {:?}", .0)]
143    ExprTypeNotYetImplemented(ExprType),
144
145    #[error("Unknown type {0}")]
146    UnknownType(String),
147
148    #[error("PyO3 Error: {0}")]
149    #[from(PyErr)]
150    Pyo3Error(PyErr),
151
152    #[error("Statement type not yet implemented: {:?}", .0)]
153    StatementNotYetImplemented(StatementType),
154    #[error("UnaryOp type not yet implemented: {:?}", .0)]
155    UnaryOpNotYetImplemented(UnaryOp),
156
157    #[error("Unknown Error: {0}")]
158    #[from(Box<dyn std::error::Error>)]
159    UnknownError(Box<dyn std::error::Error>),
160}
161
162impl From<Error> for PyErr {
163    fn from(err: Error) -> PyErr {
164        match err {
165            Error::ParseError { message, .. } 
166            | Error::SyntaxError { message, .. } => {
167                pyo3::exceptions::PySyntaxError::new_err(message)
168            },
169            Error::TypeError { message, .. } => {
170                pyo3::exceptions::PyTypeError::new_err(message)
171            },
172            Error::UnsupportedFeature { feature, .. } => {
173                pyo3::exceptions::PyNotImplementedError::new_err(format!("Unsupported feature: {}", feature))
174            },
175            Error::CodeGenError { message, .. } => {
176                pyo3::exceptions::PyRuntimeError::new_err(message)
177            },
178            Error::Pyo3Error(py_err) => py_err,
179            _ => pyo3::exceptions::PyRuntimeError::new_err(format!("{}", err)),
180        }
181    }
182}
183
184impl Error {
185    /// Create a parsing error with location and helpful guidance
186    pub fn parsing_error(
187        location: SourceLocation,
188        message: impl Into<String>,
189        help: impl Into<String>,
190    ) -> Self {
191        Error::ParseError {
192            location,
193            message: message.into(),
194            help: help.into(),
195        }
196    }
197
198    /// Create a code generation error with location and helpful guidance
199    pub fn codegen_error(
200        location: SourceLocation,
201        message: impl Into<String>,
202        help: impl Into<String>,
203    ) -> Self {
204        Error::CodeGenError {
205            location,
206            message: message.into(),
207            help: help.into(),
208        }
209    }
210
211    /// Create an unsupported feature error with location and helpful guidance
212    pub fn unsupported_feature(
213        location: SourceLocation,
214        feature: impl Into<String>,
215        help: impl Into<String>,
216    ) -> Self {
217        Error::UnsupportedFeature {
218            location,
219            feature: feature.into(),
220            help: help.into(),
221        }
222    }
223
224    /// Create a type error with location and helpful guidance
225    pub fn type_error(
226        location: SourceLocation,
227        message: impl Into<String>,
228        expected: impl Into<String>,
229        found: impl Into<String>,
230        help: impl Into<String>,
231    ) -> Self {
232        Error::TypeError {
233            location,
234            message: message.into(),
235            expected: expected.into(),
236            found: found.into(),
237            help: help.into(),
238        }
239    }
240
241    /// Create a syntax error with location and helpful guidance
242    pub fn syntax_error(
243        location: SourceLocation,
244        message: impl Into<String>,
245        help: impl Into<String>,
246    ) -> Self {
247        Error::SyntaxError {
248            location,
249            message: message.into(),
250            help: help.into(),
251        }
252    }
253}
254
255pub type Result<T> = std::result::Result<T, Error>;
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260
261    #[test]
262    fn test_error_display_unknown_type() {
263        let error = Error::UnknownType("SomeUnknownType".to_string());
264        let display = format!("{}", error);
265        assert_eq!(display, "Unknown type SomeUnknownType");
266    }
267
268    #[test]
269    fn test_error_display_unknown_error() {
270        let custom_error: Box<dyn std::error::Error> = Box::new(std::io::Error::new(
271            std::io::ErrorKind::NotFound,
272            "Test error"
273        ));
274        let error = Error::UnknownError(custom_error);
275        let display = format!("{}", error);
276        assert!(display.contains("Unknown Error"));
277        assert!(display.contains("Test error"));
278    }
279
280    #[test]
281    fn test_error_debug() {
282        let error = Error::UnknownType("TestType".to_string());
283        let debug = format!("{:?}", error);
284        assert!(debug.contains("UnknownType"));
285        assert!(debug.contains("TestType"));
286    }
287
288    #[test]
289    fn test_result_type_ok() {
290        let result: Result<i32> = Ok(42);
291        assert!(result.is_ok());
292        assert_eq!(result.unwrap(), 42);
293    }
294
295    #[test]
296    fn test_result_type_err() {
297        let result: Result<i32> = Err(Error::UnknownType("TestError".to_string()));
298        assert!(result.is_err());
299        
300        match result {
301            Err(Error::UnknownType(msg)) => assert_eq!(msg, "TestError"),
302            _ => panic!("Expected UnknownType error"),
303        }
304    }
305
306    #[test]
307    fn test_error_chaining() {
308        let result: Result<i32> = Err(Error::UnknownType("ChainTest".to_string()));
309        
310        let chained_result = result.map_err(|e| Error::UnknownError(Box::new(e)));
311        
312        assert!(chained_result.is_err());
313        match chained_result {
314            Err(Error::UnknownError(_)) => (),
315            _ => panic!("Expected chained error"),
316        }
317    }
318}