1use pyo3::PyErr;
20use thiserror::Error as ThisError;
21
22use crate::{BinOp, BoolOp, Compare, Expr, ExprType, PositionInfo, StatementType, UnaryOp};
23
24pub use anyhow_tracing::{Context, Error, Result};
27pub use anyhow_tracing::{anyhow, bail, ensure};
30
31#[derive(Debug, Clone, PartialEq)]
33pub struct SourceLocation {
34 pub filename: String,
35 pub line: Option<usize>,
36 pub column: Option<usize>,
37 pub end_line: Option<usize>,
38 pub end_column: Option<usize>,
39}
40
41impl SourceLocation {
42 pub fn new(filename: impl Into<String>) -> Self {
43 Self {
44 filename: filename.into(),
45 line: None,
46 column: None,
47 end_line: None,
48 end_column: None,
49 }
50 }
51
52 pub fn with_position(
53 filename: impl Into<String>,
54 line: Option<usize>,
55 column: Option<usize>,
56 ) -> Self {
57 Self {
58 filename: filename.into(),
59 line,
60 column,
61 end_line: None,
62 end_column: None,
63 }
64 }
65
66 pub fn with_span(
67 filename: impl Into<String>,
68 line: Option<usize>,
69 column: Option<usize>,
70 end_line: Option<usize>,
71 end_column: Option<usize>,
72 ) -> Self {
73 Self {
74 filename: filename.into(),
75 line,
76 column,
77 end_line,
78 end_column,
79 }
80 }
81
82 pub fn from_node(filename: impl Into<String>, node: &dyn PositionInfo) -> Self {
84 let (line, column, end_line, end_column) = node.position_info();
85 Self {
86 filename: filename.into(),
87 line,
88 column,
89 end_line,
90 end_column,
91 }
92 }
93}
94
95impl std::fmt::Display for SourceLocation {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 match (self.line, self.column) {
98 (Some(line), Some(col)) => {
99 if let (Some(end_line), Some(end_col)) = (self.end_line, self.end_column) {
100 if line == end_line {
101 write!(f, "{}:{}:{}-{}", self.filename, line, col, end_col)
102 } else {
103 write!(
104 f,
105 "{}:{}:{}-{}:{}",
106 self.filename, line, col, end_line, end_col
107 )
108 }
109 } else {
110 write!(f, "{}:{}:{}", self.filename, line, col)
111 }
112 }
113 (Some(line), None) => write!(f, "{}:{}", self.filename, line),
114 _ => write!(f, "{}", self.filename),
115 }
116 }
117}
118
119#[derive(Debug, ThisError)]
132#[error("parsing error at {location}")]
133pub struct ParseError {
134 pub location: SourceLocation,
135}
136
137#[derive(Debug, ThisError)]
139#[error("code generation error at {location}")]
140pub struct CodeGenError {
141 pub location: SourceLocation,
142}
143
144#[derive(Debug, ThisError)]
146#[error("unsupported feature `{feature}` at {location}")]
147pub struct UnsupportedFeature {
148 pub location: SourceLocation,
149 pub feature: String,
150}
151
152#[derive(Debug, ThisError)]
154#[error("type error at {location}")]
155pub struct TypeError {
156 pub location: SourceLocation,
157}
158
159#[derive(Debug, ThisError)]
161#[error("syntax error at {location}")]
162pub struct SyntaxError {
163 pub location: SourceLocation,
164}
165
166#[derive(Debug, ThisError)]
170#[error("BinOp type not yet implemented: {0:?}")]
171pub struct BinOpNotYetImplemented(pub BinOp);
172
173#[derive(Debug, ThisError)]
174#[error("BoolOp type not yet implemented: {0:?}")]
175pub struct BoolOpNotYetImplemented(pub BoolOp);
176
177#[derive(Debug, ThisError)]
178#[error("Compare type not yet implemented: {0:?}")]
179pub struct CompareNotYetImplemented(pub Compare);
180
181#[derive(Debug, ThisError)]
182#[error("Expr type not yet implemented: {0:?}")]
183pub struct ExprNotYetImplemented(pub Expr);
184
185#[derive(Debug, ThisError)]
186#[error("ExprType type not yet implemented: {0:?}")]
187pub struct ExprTypeNotYetImplemented(pub ExprType);
188
189#[derive(Debug, ThisError)]
190#[error("Statement type not yet implemented: {0:?}")]
191pub struct StatementNotYetImplemented(pub StatementType);
192
193#[derive(Debug, ThisError)]
194#[error("UnaryOp type not yet implemented: {0:?}")]
195pub struct UnaryOpNotYetImplemented(pub UnaryOp);
196
197#[derive(Debug, ThisError)]
198#[error("Unknown type {0}")]
199pub struct UnknownType(pub String);
200
201fn into_error<E>(typed: E) -> Error
214where
215 E: std::error::Error + Send + Sync + 'static,
216{
217 Error::from(anyhow::Error::from(typed))
218}
219
220pub fn parsing_error(
222 location: SourceLocation,
223 message: impl std::fmt::Display,
224 help: impl std::fmt::Display,
225) -> Error {
226 let location_str = location.to_string();
227 into_error(ParseError { location })
228 .with_field("location", location_str)
229 .with_field("message", message)
230 .with_field("help", help)
231}
232
233pub fn codegen_error(
235 location: SourceLocation,
236 message: impl std::fmt::Display,
237 help: impl std::fmt::Display,
238) -> Error {
239 let location_str = location.to_string();
240 into_error(CodeGenError { location })
241 .with_field("location", location_str)
242 .with_field("message", message)
243 .with_field("help", help)
244}
245
246pub fn unsupported_feature(
248 location: SourceLocation,
249 feature: impl Into<String>,
250 help: impl std::fmt::Display,
251) -> Error {
252 let feature = feature.into();
253 let location_str = location.to_string();
254 into_error(UnsupportedFeature {
255 location,
256 feature: feature.clone(),
257 })
258 .with_field("location", location_str)
259 .with_field("feature", feature)
260 .with_field("help", help)
261}
262
263pub fn type_error(
265 location: SourceLocation,
266 message: impl std::fmt::Display,
267 expected: impl std::fmt::Display,
268 found: impl std::fmt::Display,
269 help: impl std::fmt::Display,
270) -> Error {
271 let location_str = location.to_string();
272 into_error(TypeError { location })
273 .with_field("location", location_str)
274 .with_field("message", message)
275 .with_field("expected", expected)
276 .with_field("found", found)
277 .with_field("help", help)
278}
279
280pub fn syntax_error(
282 location: SourceLocation,
283 message: impl std::fmt::Display,
284 help: impl std::fmt::Display,
285) -> Error {
286 let location_str = location.to_string();
287 into_error(SyntaxError { location })
288 .with_field("location", location_str)
289 .with_field("message", message)
290 .with_field("help", help)
291}
292
293pub trait IntoErr<T> {
310 fn into_err(self) -> Result<T>;
313}
314
315impl<T, E> IntoErr<T> for std::result::Result<T, E>
316where
317 E: std::error::Error + Send + Sync + 'static,
318{
319 fn into_err(self) -> Result<T> {
320 self.map_err(|e| Error::from(anyhow::Error::from(e)))
321 }
322}
323
324pub fn err_from<E>(e: E) -> Error
326where
327 E: std::error::Error + Send + Sync + 'static,
328{
329 Error::from(anyhow::Error::from(e))
330}
331
332pub fn format_error_chain(e: &dyn std::error::Error) -> String {
336 let mut message = e.to_string();
337 let mut source = e.source();
338 while let Some(cause) = source {
339 message.push_str(&format!(": {}", cause));
340 source = cause.source();
341 }
342 message
343}
344
345pub fn error_to_pyerr(err: Error) -> PyErr {
361 use pyo3::exceptions::*;
362
363 let display = format!("{:#}", err);
364
365 if err.is::<ParseError>() || err.is::<SyntaxError>() {
366 PySyntaxError::new_err(display)
367 } else if err.is::<TypeError>() {
368 PyTypeError::new_err(display)
369 } else if err.is::<UnsupportedFeature>() {
370 PyNotImplementedError::new_err(display)
371 } else if err.is::<CodeGenError>() {
372 PyRuntimeError::new_err(display)
373 } else {
374 PyRuntimeError::new_err(display)
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
383 fn test_unknown_type_downcast() {
384 let err: Error = err_from(UnknownType("SomeUnknownType".to_string()));
385 let display = format!("{}", err);
386 assert!(display.contains("SomeUnknownType"));
387 let inner = err.downcast_ref::<UnknownType>().expect("should downcast");
388 assert_eq!(inner.0, "SomeUnknownType");
389 }
390
391 #[test]
392 fn test_parsing_error_carries_named_fields() {
393 let loc = SourceLocation::new("foo.py");
394 let err = parsing_error(loc.clone(), "boom", "fix it");
395
396 let typed = err.downcast_ref::<ParseError>().expect("downcast");
398 assert_eq!(typed.location, loc);
399
400 assert_eq!(err.get_field("message"), Some("boom"));
403 assert_eq!(err.get_field("help"), Some("fix it"));
404 assert!(err.get_field("location").is_some());
405 }
406
407 #[test]
408 fn test_unsupported_feature_named_fields() {
409 let loc = SourceLocation::new("bar.py");
410 let err = unsupported_feature(loc, "match-statement", "use if/elif chains");
411 assert_eq!(err.get_field("feature"), Some("match-statement"));
412 assert_eq!(err.get_field("help"), Some("use if/elif chains"));
413 let typed = err.downcast_ref::<UnsupportedFeature>().expect("downcast");
414 assert_eq!(typed.feature, "match-statement");
415 }
416
417 #[test]
418 fn test_type_error_carries_expected_and_found() {
419 let loc = SourceLocation::new("baz.py");
420 let err = type_error(loc, "mismatch", "int", "str", "convert it");
421 assert_eq!(err.get_field("expected"), Some("int"));
422 assert_eq!(err.get_field("found"), Some("str"));
423 assert!(err.downcast_ref::<TypeError>().is_some());
424 }
425
426 #[test]
427 fn test_into_err_adapter() {
428 let pyresult: std::result::Result<(), std::io::Error> =
429 Err(std::io::Error::new(std::io::ErrorKind::NotFound, "missing"));
430 let r: Result<()> = IntoErr::into_err(pyresult);
431 let e = r.expect_err("must be err");
432 let display = format!("{}", e);
433 assert!(display.contains("missing"));
434 }
435
436 #[test]
437 fn test_result_ok_passthrough() {
438 let r: Result<i32> = Ok(42);
439 assert_eq!(r.unwrap(), 42);
440 }
441}