use pyo3::PyErr;
use thiserror::Error as ThisError;
use crate::{BinOp, BoolOp, Compare, Expr, ExprType, PositionInfo, StatementType, UnaryOp};
pub use anyhow_tracing::{Context, Error, Result};
pub use anyhow_tracing::{anyhow, bail, ensure};
#[derive(Debug, Clone, PartialEq)]
pub struct SourceLocation {
pub filename: String,
pub line: Option<usize>,
pub column: Option<usize>,
pub end_line: Option<usize>,
pub end_column: Option<usize>,
}
impl SourceLocation {
pub fn new(filename: impl Into<String>) -> Self {
Self {
filename: filename.into(),
line: None,
column: None,
end_line: None,
end_column: None,
}
}
pub fn with_position(
filename: impl Into<String>,
line: Option<usize>,
column: Option<usize>,
) -> Self {
Self {
filename: filename.into(),
line,
column,
end_line: None,
end_column: None,
}
}
pub fn with_span(
filename: impl Into<String>,
line: Option<usize>,
column: Option<usize>,
end_line: Option<usize>,
end_column: Option<usize>,
) -> Self {
Self {
filename: filename.into(),
line,
column,
end_line,
end_column,
}
}
pub fn from_node(filename: impl Into<String>, node: &dyn PositionInfo) -> Self {
let (line, column, end_line, end_column) = node.position_info();
Self {
filename: filename.into(),
line,
column,
end_line,
end_column,
}
}
}
impl std::fmt::Display for SourceLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match (self.line, self.column) {
(Some(line), Some(col)) => {
if let (Some(end_line), Some(end_col)) = (self.end_line, self.end_column) {
if line == end_line {
write!(f, "{}:{}:{}-{}", self.filename, line, col, end_col)
} else {
write!(
f,
"{}:{}:{}-{}:{}",
self.filename, line, col, end_line, end_col
)
}
} else {
write!(f, "{}:{}:{}", self.filename, line, col)
}
}
(Some(line), None) => write!(f, "{}:{}", self.filename, line),
_ => write!(f, "{}", self.filename),
}
}
}
#[derive(Debug, ThisError)]
#[error("parsing error at {location}")]
pub struct ParseError {
pub location: SourceLocation,
}
#[derive(Debug, ThisError)]
#[error("code generation error at {location}")]
pub struct CodeGenError {
pub location: SourceLocation,
}
#[derive(Debug, ThisError)]
#[error("unsupported feature `{feature}` at {location}")]
pub struct UnsupportedFeature {
pub location: SourceLocation,
pub feature: String,
}
#[derive(Debug, ThisError)]
#[error("type error at {location}")]
pub struct TypeError {
pub location: SourceLocation,
}
#[derive(Debug, ThisError)]
#[error("syntax error at {location}")]
pub struct SyntaxError {
pub location: SourceLocation,
}
#[derive(Debug, ThisError)]
#[error("BinOp type not yet implemented: {0:?}")]
pub struct BinOpNotYetImplemented(pub BinOp);
#[derive(Debug, ThisError)]
#[error("BoolOp type not yet implemented: {0:?}")]
pub struct BoolOpNotYetImplemented(pub BoolOp);
#[derive(Debug, ThisError)]
#[error("Compare type not yet implemented: {0:?}")]
pub struct CompareNotYetImplemented(pub Compare);
#[derive(Debug, ThisError)]
#[error("Expr type not yet implemented: {0:?}")]
pub struct ExprNotYetImplemented(pub Expr);
#[derive(Debug, ThisError)]
#[error("ExprType type not yet implemented: {0:?}")]
pub struct ExprTypeNotYetImplemented(pub ExprType);
#[derive(Debug, ThisError)]
#[error("Statement type not yet implemented: {0:?}")]
pub struct StatementNotYetImplemented(pub StatementType);
#[derive(Debug, ThisError)]
#[error("UnaryOp type not yet implemented: {0:?}")]
pub struct UnaryOpNotYetImplemented(pub UnaryOp);
#[derive(Debug, ThisError)]
#[error("Unknown type {0}")]
pub struct UnknownType(pub String);
fn into_error<E>(typed: E) -> Error
where
E: std::error::Error + Send + Sync + 'static,
{
Error::from(anyhow::Error::from(typed))
}
pub fn parsing_error(
location: SourceLocation,
message: impl std::fmt::Display,
help: impl std::fmt::Display,
) -> Error {
let location_str = location.to_string();
into_error(ParseError { location })
.with_field("location", location_str)
.with_field("message", message)
.with_field("help", help)
}
pub fn codegen_error(
location: SourceLocation,
message: impl std::fmt::Display,
help: impl std::fmt::Display,
) -> Error {
let location_str = location.to_string();
into_error(CodeGenError { location })
.with_field("location", location_str)
.with_field("message", message)
.with_field("help", help)
}
pub fn unsupported_feature(
location: SourceLocation,
feature: impl Into<String>,
help: impl std::fmt::Display,
) -> Error {
let feature = feature.into();
let location_str = location.to_string();
into_error(UnsupportedFeature {
location,
feature: feature.clone(),
})
.with_field("location", location_str)
.with_field("feature", feature)
.with_field("help", help)
}
pub fn type_error(
location: SourceLocation,
message: impl std::fmt::Display,
expected: impl std::fmt::Display,
found: impl std::fmt::Display,
help: impl std::fmt::Display,
) -> Error {
let location_str = location.to_string();
into_error(TypeError { location })
.with_field("location", location_str)
.with_field("message", message)
.with_field("expected", expected)
.with_field("found", found)
.with_field("help", help)
}
pub fn syntax_error(
location: SourceLocation,
message: impl std::fmt::Display,
help: impl std::fmt::Display,
) -> Error {
let location_str = location.to_string();
into_error(SyntaxError { location })
.with_field("location", location_str)
.with_field("message", message)
.with_field("help", help)
}
pub trait IntoErr<T> {
fn into_err(self) -> Result<T>;
}
impl<T, E> IntoErr<T> for std::result::Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn into_err(self) -> Result<T> {
self.map_err(|e| Error::from(anyhow::Error::from(e)))
}
}
pub fn err_from<E>(e: E) -> Error
where
E: std::error::Error + Send + Sync + 'static,
{
Error::from(anyhow::Error::from(e))
}
pub fn format_error_chain(e: &dyn std::error::Error) -> String {
let mut message = e.to_string();
let mut source = e.source();
while let Some(cause) = source {
message.push_str(&format!(": {}", cause));
source = cause.source();
}
message
}
pub fn error_to_pyerr(err: Error) -> PyErr {
use pyo3::exceptions::*;
let display = format!("{:#}", err);
if err.is::<ParseError>() || err.is::<SyntaxError>() {
PySyntaxError::new_err(display)
} else if err.is::<TypeError>() {
PyTypeError::new_err(display)
} else if err.is::<UnsupportedFeature>() {
PyNotImplementedError::new_err(display)
} else if err.is::<CodeGenError>() {
PyRuntimeError::new_err(display)
} else {
PyRuntimeError::new_err(display)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unknown_type_downcast() {
let err: Error = err_from(UnknownType("SomeUnknownType".to_string()));
let display = format!("{}", err);
assert!(display.contains("SomeUnknownType"));
let inner = err.downcast_ref::<UnknownType>().expect("should downcast");
assert_eq!(inner.0, "SomeUnknownType");
}
#[test]
fn test_parsing_error_carries_named_fields() {
let loc = SourceLocation::new("foo.py");
let err = parsing_error(loc.clone(), "boom", "fix it");
let typed = err.downcast_ref::<ParseError>().expect("downcast");
assert_eq!(typed.location, loc);
assert_eq!(err.get_field("message"), Some("boom"));
assert_eq!(err.get_field("help"), Some("fix it"));
assert!(err.get_field("location").is_some());
}
#[test]
fn test_unsupported_feature_named_fields() {
let loc = SourceLocation::new("bar.py");
let err = unsupported_feature(loc, "match-statement", "use if/elif chains");
assert_eq!(err.get_field("feature"), Some("match-statement"));
assert_eq!(err.get_field("help"), Some("use if/elif chains"));
let typed = err.downcast_ref::<UnsupportedFeature>().expect("downcast");
assert_eq!(typed.feature, "match-statement");
}
#[test]
fn test_type_error_carries_expected_and_found() {
let loc = SourceLocation::new("baz.py");
let err = type_error(loc, "mismatch", "int", "str", "convert it");
assert_eq!(err.get_field("expected"), Some("int"));
assert_eq!(err.get_field("found"), Some("str"));
assert!(err.downcast_ref::<TypeError>().is_some());
}
#[test]
fn test_into_err_adapter() {
let pyresult: std::result::Result<(), std::io::Error> =
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "missing"));
let r: Result<()> = IntoErr::into_err(pyresult);
let e = r.expect_err("must be err");
let display = format!("{}", e);
assert!(display.contains("missing"));
}
#[test]
fn test_result_ok_passthrough() {
let r: Result<i32> = Ok(42);
assert_eq!(r.unwrap(), 42);
}
}