use std::fmt;
use crate::exception::Exception;
use crate::object::Object;
macro_rules! hierarchy {
($( $(#[$about:meta])* $name:ident $(=> $base:ident)? ),+ $(,)?) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Kind {
$( $(#[$about])* $name, )+
}
impl Kind {
pub const ALL: &'static [Kind] = &[ $( Kind::$name, )+ ];
#[must_use]
pub const fn name(self) -> &'static str {
match self { $( Kind::$name => stringify!($name), )+ }
}
#[must_use]
pub const fn base(self) -> Option<Kind> {
match self { $( Kind::$name => hierarchy!(@base $($base)?), )+ }
}
}
};
(@base) => { None };
(@base $base:ident) => { Some(Kind::$base) };
}
hierarchy! {
BaseException,
Exception => BaseException,
ArithmeticError => Exception,
FloatingPointError => ArithmeticError,
OverflowError => ArithmeticError,
ZeroDivisionError => ArithmeticError,
AssertionError => Exception,
AttributeError => Exception,
BufferError => Exception,
EOFError => Exception,
ImportError => Exception,
ModuleNotFoundError => ImportError,
LookupError => Exception,
IndexError => LookupError,
KeyError => LookupError,
MemoryError => Exception,
NameError => Exception,
UnboundLocalError => NameError,
OSError => Exception,
BlockingIOError => OSError,
ChildProcessError => OSError,
ConnectionError => OSError,
BrokenPipeError => ConnectionError,
ConnectionAbortedError => ConnectionError,
ConnectionRefusedError => ConnectionError,
ConnectionResetError => ConnectionError,
FileExistsError => OSError,
FileNotFoundError => OSError,
InterruptedError => OSError,
IsADirectoryError => OSError,
NotADirectoryError => OSError,
PermissionError => OSError,
ProcessLookupError => OSError,
TimeoutError => OSError,
ReferenceError => Exception,
RuntimeError => Exception,
NotImplementedError => RuntimeError,
PythonFinalizationError => RuntimeError,
RecursionError => RuntimeError,
StopAsyncIteration => Exception,
StopIteration => Exception,
SyntaxError => Exception,
IndentationError => SyntaxError,
TabError => IndentationError,
SystemError => Exception,
TypeError => Exception,
ValueError => Exception,
UnicodeError => ValueError,
UnicodeDecodeError => UnicodeError,
UnicodeEncodeError => UnicodeError,
UnicodeTranslateError => UnicodeError,
Warning => Exception,
BytesWarning => Warning,
DeprecationWarning => Warning,
EncodingWarning => Warning,
FutureWarning => Warning,
ImportWarning => Warning,
PendingDeprecationWarning => Warning,
ResourceWarning => Warning,
RuntimeWarning => Warning,
SyntaxWarning => Warning,
UnicodeWarning => Warning,
UserWarning => Warning,
GeneratorExit => BaseException,
KeyboardInterrupt => BaseException,
SystemExit => BaseException,
}
impl Kind {
#[must_use]
pub fn derives_from(self, base: Kind) -> bool {
let mut at = Some(self);
while let Some(kind) = at {
if kind == base {
return true;
}
at = kind.base();
}
false
}
}
impl fmt::Display for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone)]
pub struct Error {
pub kind: Kind,
pub message: String,
value: Option<Box<Object>>,
}
impl Error {
#[must_use]
pub fn new(kind: Kind, message: impl Into<String>) -> Self {
Error {
kind,
message: message.into(),
value: None,
}
}
#[must_use]
pub fn type_error(message: impl Into<String>) -> Self {
Error::new(Kind::TypeError, message)
}
#[must_use]
pub fn value_error(message: impl Into<String>) -> Self {
Error::new(Kind::ValueError, message)
}
#[must_use]
pub fn zero_division(message: impl Into<String>) -> Self {
Error::new(Kind::ZeroDivisionError, message)
}
#[must_use]
pub fn overflow(message: impl Into<String>) -> Self {
Error::new(Kind::OverflowError, message)
}
#[must_use]
pub fn raised(kind: Kind, args: Vec<Object>) -> Self {
let raised = Exception::new(kind, args);
let message = raised.message();
Error::new(kind, message).with_value(Object::native(raised))
}
#[must_use]
pub fn with_value(mut self, value: Object) -> Self {
self.value = Some(Box::new(value));
self
}
#[must_use]
pub fn value(&self) -> Option<&Object> {
self.value.as_deref()
}
#[must_use]
pub fn instance(&self) -> Object {
if let Some(value) = self.value() {
return value.clone();
}
let args = if self.message.is_empty() {
Vec::new()
} else {
vec![Object::str(self.message.as_str())]
};
Object::native(Exception::new(self.kind, args))
}
fn chain(&self) -> Vec<(String, &'static str)> {
let mut chain = Vec::new();
let mut seen: Vec<*const Exception> = Vec::new();
let head = self.value.as_deref().and_then(Object::exception);
if let Some(head) = head {
seen.push(head);
}
let mut next = head.and_then(printed_above);
while let Some((value, sentence)) = next {
let Some(exception) = value.exception() else {
break;
};
let address: *const Exception = exception;
if seen.contains(&address) {
break;
}
seen.push(address);
chain.push((last_line(exception.kind(), &exception.message()), sentence));
next = printed_above(exception);
}
chain.reverse();
chain
}
}
fn printed_above(exception: &Exception) -> Option<(Object, &'static str)> {
if let Some(cause) = exception.cause() {
return Some((
cause,
"The above exception was the direct cause of the following exception:",
));
}
if exception.suppresses_context() {
return None;
}
Some((
exception.context()?,
"During handling of the above exception, another exception occurred:",
))
}
fn last_line(kind: Kind, message: &str) -> String {
if message.is_empty() {
kind.name().to_owned()
} else {
format!("{kind}: {message}")
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (line, sentence) in self.chain() {
writeln!(f, "{line}\n\n{sentence}\n")?;
}
f.write_str(&last_line(self.kind, &self.message))
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_exception_prints_the_way_the_last_line_of_a_traceback_does() {
assert_eq!(
Error::type_error("unsupported operand type(s) for +: 'int' and 'str'").to_string(),
"TypeError: unsupported operand type(s) for +: 'int' and 'str'"
);
assert_eq!(
Error::zero_division("division by zero").to_string(),
"ZeroDivisionError: division by zero"
);
}
#[test]
fn an_exception_with_nothing_to_say_prints_its_name_alone() {
assert_eq!(Error::new(Kind::MemoryError, "").to_string(), "MemoryError");
}
#[test]
fn a_class_derives_from_itself_and_from_everything_above_it() {
assert!(Kind::ZeroDivisionError.derives_from(Kind::ZeroDivisionError));
assert!(Kind::ZeroDivisionError.derives_from(Kind::ArithmeticError));
assert!(Kind::ZeroDivisionError.derives_from(Kind::Exception));
assert!(Kind::ZeroDivisionError.derives_from(Kind::BaseException));
assert!(!Kind::ZeroDivisionError.derives_from(Kind::ValueError));
}
#[test]
fn the_three_that_are_not_exceptions_hang_off_the_root() {
for kind in [
Kind::GeneratorExit,
Kind::KeyboardInterrupt,
Kind::SystemExit,
] {
assert!(kind.derives_from(Kind::BaseException));
assert!(!kind.derives_from(Kind::Exception));
}
}
#[test]
fn an_error_that_never_had_an_object_grows_one_when_it_is_caught() {
let caught = Error::zero_division("division by zero").instance();
assert_eq!(caught.repr(), "ZeroDivisionError('division by zero')");
assert_eq!(
Error::new(Kind::MemoryError, "").instance().repr(),
"MemoryError()"
);
}
#[test]
fn an_error_a_program_raised_is_caught_as_the_object_it_raised() {
let raised = Object::native(Exception::new(Kind::ValueError, vec![Object::str("x")]));
let error = Error::new(Kind::ValueError, "x").with_value(raised.clone());
assert!(error.instance().is(&raised));
}
#[test]
fn an_error_built_from_its_arguments_keeps_them() {
let error = Error::raised(Kind::KeyError, vec![Object::str("k")]);
assert_eq!(error.to_string(), "KeyError: 'k'");
assert_eq!(error.instance().repr(), "KeyError('k')");
}
#[test]
fn every_class_is_reachable_from_the_root() {
for &kind in Kind::ALL {
assert!(
kind.derives_from(Kind::BaseException),
"{kind} does not derive from BaseException"
);
}
assert_eq!(Kind::BaseException.base(), None);
}
}