use std::any::Any;
use std::cell::{Cell, RefCell};
use crate::error::{Error, Kind, Result};
use crate::native::Native;
use crate::object::Object;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Class {
kind: Kind,
}
impl Class {
#[must_use]
pub const fn new(kind: Kind) -> Self {
Class { kind }
}
#[must_use]
pub const fn kind(self) -> Kind {
self.kind
}
#[must_use]
pub fn instance(self, args: Vec<Object>) -> Object {
Object::native(Exception::new(self.kind, args))
}
}
impl Native for Class {
fn type_name(&self) -> &str {
"type"
}
fn repr(&self) -> String {
format!("<class '{}'>", self.kind.name())
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug)]
pub struct Exception {
kind: Kind,
args: Box<[Object]>,
cause: RefCell<Option<Object>>,
context: RefCell<Option<Object>>,
suppress: Cell<bool>,
}
impl Exception {
#[must_use]
pub fn new(kind: Kind, args: Vec<Object>) -> Self {
Exception {
kind,
args: args.into_boxed_slice(),
cause: RefCell::new(None),
context: RefCell::new(None),
suppress: Cell::new(false),
}
}
#[must_use]
pub const fn kind(&self) -> Kind {
self.kind
}
#[must_use]
pub fn args(&self) -> &[Object] {
&self.args
}
#[must_use]
pub fn cause(&self) -> Option<Object> {
self.cause.borrow().clone()
}
pub fn raised_from(&self, cause: Option<Object>) {
*self.cause.borrow_mut() = cause;
self.suppress.set(true);
}
#[must_use]
pub fn context(&self) -> Option<Object> {
self.context.borrow().clone()
}
#[must_use]
pub fn suppresses_context(&self) -> bool {
self.suppress.get()
}
#[must_use]
pub fn message(&self) -> String {
match &*self.args {
[] => String::new(),
[only] if self.kind == Kind::KeyError => only.repr(),
[only] => only.display(),
many => Object::tuple(many.to_vec()).repr(),
}
}
}
impl Native for Exception {
fn type_name(&self) -> &str {
self.kind.name()
}
fn repr(&self) -> String {
let args: Vec<String> = self.args.iter().map(Object::repr).collect();
format!("{}({})", self.kind.name(), args.join(", "))
}
fn display(&self) -> String {
self.message()
}
fn truthy(&self) -> bool {
true
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[must_use]
pub fn instance_of(value: &Object) -> Option<Object> {
if value.exception().is_some() {
return Some(value.clone());
}
let class = value.downcast::<Class>()?;
Some(class.instance(Vec::new()))
}
pub fn matches(raised: &Exception, test: &Object) -> Result<bool> {
if let Object::Tuple(members) = test {
for member in members.iter() {
if caught_by(raised, member)? {
return Ok(true);
}
}
return Ok(false);
}
caught_by(raised, test)
}
fn caught_by(raised: &Exception, test: &Object) -> Result<bool> {
let Some(class) = test.downcast::<Class>() else {
return Err(Error::type_error(
"catching classes that do not inherit from BaseException is not allowed",
));
};
Ok(raised.kind().derives_from(class.kind()))
}
#[must_use]
pub fn classes() -> Vec<(&'static str, Object)> {
Kind::ALL
.iter()
.map(|&kind| (kind.name(), Object::native(Class::new(kind))))
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Exit {
Report(String),
Status(u8),
}
#[must_use]
pub fn uncaught(error: &Error) -> Exit {
if error.kind != Kind::SystemExit {
return Exit::Report(error.to_string());
}
let code = match error
.value()
.and_then(Object::exception)
.map(Exception::args)
{
None | Some([]) => Object::None,
Some([only]) => only.clone(),
Some(many) => Object::tuple(many.to_vec()),
};
match code {
Object::None => Exit::Status(0),
Object::Bool(value) => Exit::Status(u8::from(value)),
Object::Int(value) => Exit::Status(value.to_i64().map_or(255, status)),
other => Exit::Report(other.display()),
}
}
fn status(code: i64) -> u8 {
u8::try_from(code.rem_euclid(256)).unwrap_or(255)
}
pub fn raised_while_handling(raised: &Object, handled: &Object) {
let (Some(new), Some(old)) = (raised.exception(), handled.exception()) else {
return;
};
if std::ptr::eq(new, old) {
return;
}
let mut at = handled.clone();
loop {
let next = {
let Some(exception) = at.exception() else {
break;
};
let Some(context) = exception.context() else {
break;
};
if context
.exception()
.is_some_and(|link| std::ptr::eq(link, new))
{
*exception.context.borrow_mut() = None;
break;
}
context
};
at = next;
}
*new.context.borrow_mut() = Some(handled.clone());
}
#[must_use]
pub fn reraise(exc: &Object) -> Error {
let Some(exception) = exc.exception() else {
return Error::type_error("exceptions must derive from BaseException");
};
Error::new(exception.kind(), exception.message()).with_value(exc.clone())
}
#[must_use]
pub fn raise(exc: Option<&Object>, cause: Option<&Object>) -> Error {
let Some(exc) = exc else {
return Error::new(Kind::RuntimeError, "No active exception to reraise");
};
let Some(raised) = instance_of(exc) else {
return Error::type_error("exceptions must derive from BaseException");
};
let from = match cause {
None | Some(Object::None) => None,
Some(cause) => match instance_of(cause) {
Some(from) => Some(from),
None => {
return Error::type_error("exception causes must derive from BaseException");
}
},
};
let error = {
let Some(exception) = raised.exception() else {
unreachable!("what instance_of gives back is an exception or nothing")
};
if cause.is_some() {
exception.raised_from(from);
}
Error::new(exception.kind(), exception.message())
};
error.with_value(raised)
}
#[cfg(test)]
mod tests {
use super::*;
fn exception(kind: Kind, args: Vec<Object>) -> Exception {
Exception::new(kind, args)
}
#[test]
fn a_class_prints_the_way_a_class_does_and_an_instance_the_way_a_call_does() {
assert_eq!(Class::new(Kind::ValueError).repr(), "<class 'ValueError'>");
assert_eq!(Class::new(Kind::ValueError).type_name(), "type");
assert_eq!(
exception(Kind::ValueError, Vec::new()).repr(),
"ValueError()"
);
assert_eq!(
exception(Kind::ValueError, vec![Object::str("x")]).repr(),
"ValueError('x')"
);
assert_eq!(
exception(Kind::ValueError, vec![Object::int(1), Object::int(2)]).repr(),
"ValueError(1, 2)"
);
}
#[test]
fn what_an_exception_says_is_its_arguments_rather_than_its_repr() {
assert_eq!(exception(Kind::ValueError, Vec::new()).message(), "");
assert_eq!(
exception(Kind::ValueError, vec![Object::str("boom")]).message(),
"boom"
);
assert_eq!(
exception(Kind::ValueError, vec![Object::int(1), Object::int(2)]).message(),
"(1, 2)"
);
}
#[test]
fn a_key_error_says_its_key_the_way_repr_would() {
assert_eq!(
exception(Kind::KeyError, vec![Object::str("k")]).message(),
"'k'"
);
assert_eq!(
exception(Kind::KeyError, vec![Object::str("")]).message(),
"''"
);
assert_eq!(
exception(Kind::KeyError, vec![Object::int(1), Object::int(2)]).message(),
"(1, 2)"
);
}
#[test]
fn a_clause_catches_its_class_and_everything_under_it() {
let raised = exception(Kind::ZeroDivisionError, Vec::new());
for kind in [
Kind::ZeroDivisionError,
Kind::ArithmeticError,
Kind::Exception,
Kind::BaseException,
] {
let test = Object::native(Class::new(kind));
assert!(
matches(&raised, &test).expect("a class is a clause"),
"{kind}"
);
}
let test = Object::native(Class::new(Kind::ValueError));
assert!(!matches(&raised, &test).expect("a class is a clause"));
}
#[test]
fn a_tuple_catches_what_any_of_its_members_catches() {
let raised = exception(Kind::ValueError, Vec::new());
let class = |kind| Object::native(Class::new(kind));
let test = Object::tuple(vec![class(Kind::KeyError), class(Kind::ValueError)]);
assert!(matches(&raised, &test).expect("a tuple is a clause"));
let test = Object::tuple(vec![class(Kind::KeyError), class(Kind::TypeError)]);
assert!(!matches(&raised, &test).expect("a tuple is a clause"));
assert!(!matches(&raised, &Object::tuple(Vec::new())).expect("a tuple is a clause"));
let nested = Object::tuple(vec![Object::tuple(vec![class(Kind::ValueError)])]);
assert!(matches(&raised, &nested).is_err());
}
#[test]
fn a_clause_that_names_something_that_is_not_a_class_says_so() {
let raised = exception(Kind::ValueError, Vec::new());
let error = matches(&raised, &Object::int(5)).expect_err("a number is not a clause");
assert_eq!(
error.to_string(),
"TypeError: catching classes that do not inherit from BaseException is not allowed"
);
let instance = Object::native(exception(Kind::ValueError, Vec::new()));
assert!(matches(&raised, &instance).is_err());
}
#[test]
fn a_class_stands_for_an_instance_of_it_and_a_number_stands_for_nothing() {
let class = Object::native(Class::new(Kind::ValueError));
let made = instance_of(&class).expect("a class is something to raise");
assert_eq!(made.repr(), "ValueError()");
assert!(instance_of(&Object::int(5)).is_none());
assert!(instance_of(&Object::None).is_none());
}
#[test]
fn an_instance_stands_for_itself() {
let raised = Object::native(exception(Kind::ValueError, vec![Object::str("x")]));
let again = instance_of(&raised).expect("an instance is something to raise");
assert!(raised.is(&again));
}
#[test]
fn every_builtin_class_is_bound_to_its_own_name() {
let bound = classes();
assert_eq!(bound.len(), Kind::ALL.len());
for (name, value) in &bound {
let class = value.downcast::<Class>().expect("a class is bound");
assert_eq!(class.kind().name(), *name);
}
}
#[test]
fn raising_a_class_and_raising_an_instance_of_it_say_the_same_thing() {
let class = Object::native(Class::new(Kind::ValueError));
assert_eq!(raise(Some(&class), None).to_string(), "ValueError");
let instance = Object::native(exception(Kind::ValueError, vec![Object::str("boom")]));
assert_eq!(raise(Some(&instance), None).to_string(), "ValueError: boom");
}
#[test]
fn raising_something_that_is_not_an_exception_says_so() {
assert_eq!(
raise(Some(&Object::int(5)), None).to_string(),
"TypeError: exceptions must derive from BaseException"
);
let cause = Object::int(5);
let raised = Object::native(exception(Kind::ValueError, Vec::new()));
assert_eq!(
raise(Some(&raised), Some(&cause)).to_string(),
"TypeError: exception causes must derive from BaseException"
);
}
#[test]
fn a_bare_raise_has_nothing_to_re_raise() {
assert_eq!(
raise(None, None).to_string(),
"RuntimeError: No active exception to reraise"
);
}
#[test]
fn a_cause_prints_above_the_exception_it_caused() {
let cause = Object::native(exception(Kind::KeyError, vec![Object::str("k")]));
let raised = Object::native(exception(Kind::ValueError, vec![Object::str("boom")]));
assert_eq!(
raise(Some(&raised), Some(&cause)).to_string(),
"KeyError: 'k'\n\nThe above exception was the direct cause of the \
following exception:\n\nValueError: boom"
);
assert_eq!(
raise(Some(&raised), Some(&Object::None)).to_string(),
"ValueError: boom"
);
}
#[test]
fn an_uncaught_exception_is_reported() {
assert_eq!(
uncaught(&Error::zero_division("division by zero")),
Exit::Report("ZeroDivisionError: division by zero".to_owned())
);
let raised = Object::native(exception(Kind::ValueError, vec![Object::str("boom")]));
assert_eq!(
uncaught(&raise(Some(&raised), None)),
Exit::Report("ValueError: boom".to_owned())
);
}
#[test]
fn a_system_exit_given_a_number_is_that_status() {
let status = |args: Vec<Object>| {
let raised = Object::native(exception(Kind::SystemExit, args));
uncaught(&raise(Some(&raised), None))
};
assert_eq!(status(Vec::new()), Exit::Status(0));
assert_eq!(status(vec![Object::None]), Exit::Status(0));
assert_eq!(status(vec![Object::int(3)]), Exit::Status(3));
assert_eq!(status(vec![Object::int(256)]), Exit::Status(0));
assert_eq!(status(vec![Object::int(-1)]), Exit::Status(255));
assert_eq!(status(vec![Object::Bool(true)]), Exit::Status(1));
assert_eq!(status(vec![Object::Bool(false)]), Exit::Status(0));
}
#[test]
fn a_system_exit_given_anything_else_is_a_message() {
let raised = Object::native(exception(Kind::SystemExit, vec![Object::str("no good")]));
assert_eq!(
uncaught(&raise(Some(&raised), None)),
Exit::Report("no good".to_owned())
);
let pair = Object::native(exception(
Kind::SystemExit,
vec![Object::str("a"), Object::str("b")],
));
assert_eq!(
uncaught(&raise(Some(&pair), None)),
Exit::Report("('a', 'b')".to_owned())
);
}
#[test]
fn what_was_being_handled_prints_above_what_was_raised_while_handling_it() {
let handled = Object::native(exception(Kind::ValueError, vec![Object::str("a")]));
let raised = Object::native(exception(Kind::KeyError, vec![Object::str("b")]));
raised_while_handling(&raised, &handled);
assert_eq!(
raise(Some(&raised), None).to_string(),
"ValueError: a\n\nDuring handling of the above exception, another \
exception occurred:\n\nKeyError: 'b'"
);
}
#[test]
fn a_cause_is_printed_instead_of_a_context_and_from_none_prints_neither() {
let handled = Object::native(exception(Kind::ValueError, vec![Object::str("a")]));
let cause = Object::native(exception(Kind::IndexError, vec![Object::str("i")]));
let raised = Object::native(exception(Kind::KeyError, vec![Object::str("b")]));
raised_while_handling(&raised, &handled);
assert_eq!(
raise(Some(&raised), Some(&cause)).to_string(),
"IndexError: i\n\nThe above exception was the direct cause of the \
following exception:\n\nKeyError: 'b'"
);
assert_eq!(
raise(Some(&raised), Some(&Object::None)).to_string(),
"KeyError: 'b'"
);
}
#[test]
fn an_exception_is_not_the_context_of_itself() {
let raised = Object::native(exception(Kind::ValueError, vec![Object::str("a")]));
raised_while_handling(&raised, &raised);
assert_eq!(raise(Some(&raised), None).to_string(), "ValueError: a");
}
#[test]
fn making_a_context_cuts_whatever_link_would_close_a_ring() {
let a = Object::native(exception(Kind::ValueError, vec![Object::str("a")]));
let b = Object::native(exception(Kind::KeyError, vec![Object::str("b")]));
let c = Object::native(exception(Kind::IndexError, vec![Object::str("c")]));
raised_while_handling(&b, &a);
raised_while_handling(&c, &b);
raised_while_handling(&a, &c);
assert_eq!(
raise(Some(&a), None).to_string(),
"KeyError: 'b'\n\nDuring handling of the above exception, another \
exception occurred:\n\nIndexError: c\n\nDuring handling of the \
above exception, another exception occurred:\n\nValueError: a"
);
}
#[test]
fn a_reraise_says_the_same_thing_and_settles_nothing_again() {
let handled = Object::native(exception(Kind::ValueError, vec![Object::str("a")]));
let raised = Object::native(exception(Kind::KeyError, vec![Object::str("b")]));
raised_while_handling(&raised, &handled);
let put_back = reraise(&raised);
assert!(put_back.instance().is(&raised));
assert_eq!(
put_back.to_string(),
"ValueError: a\n\nDuring handling of the above exception, another \
exception occurred:\n\nKeyError: 'b'"
);
}
#[test]
fn an_exception_raised_from_itself_prints_once() {
let raised = Object::native(exception(Kind::ValueError, vec![Object::str("x")]));
assert_eq!(
raise(Some(&raised), Some(&raised)).to_string(),
"ValueError: x"
);
}
}