Skip to main content

kohebi_core/
exception.rs

1//! An exception as something a program holds, rather than as something the
2//! runtime returns.
3//!
4//! Two types, because Python has two: `ValueError` is a class and
5//! `ValueError('x')` is an instance of it, and a `raise` accepts either. The
6//! class is [`Class`] and the instance is [`Exception`], and both are values
7//! that go in variables, get passed to functions and get printed.
8//!
9//! ## Why these are here and not above
10//!
11//! [`Native`] exists so that the runtime can define types this crate does not
12//! know the shape of, and its documentation names exceptions as one of them.
13//! That turned out to be true of a function and an iterator, which need to know
14//! where output goes and how the interpreter steps, and not true of these. An
15//! exception is a class name and a tuple of arguments. Nothing about it depends
16//! on the interpreter, and [`Error`] has to be able to carry one, so it lives
17//! next to [`Error`] rather than a crate away from it.
18//!
19//! ## What a class is missing
20//!
21//! Attributes. `e.args`, `e.__cause__` and the `errno` an `OSError` is supposed
22//! to have are all readable in CPython and none of them are readable here,
23//! because there is no attribute access yet. The arguments are kept anyway,
24//! since `str` and `repr` are made out of them and since the reading is what is
25//! missing rather than the data.
26//!
27//! Constructor signatures, for the handful that have one. `OSError(2, 'x')`
28//! sets `errno` and comes back as a `FileNotFoundError` in CPython, and
29//! `UnicodeDecodeError` demands five arguments. Here every class takes whatever
30//! it is given. That is a difference worth writing down and not one worth
31//! fixing before the attributes those arguments would be stored in exist.
32
33use std::any::Any;
34use std::cell::{Cell, RefCell};
35
36use crate::error::{Error, Kind, Result};
37use crate::native::Native;
38use crate::object::Object;
39
40/// A builtin exception class, as a value.
41///
42/// This is what the name `ValueError` is bound to. Calling it makes an
43/// [`Exception`], which is the only thing it does, and which is why it holds
44/// nothing but the [`Kind`] it constructs.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub struct Class {
47    kind: Kind,
48}
49
50impl Class {
51    /// The class for this kind.
52    #[must_use]
53    pub const fn new(kind: Kind) -> Self {
54        Class { kind }
55    }
56
57    /// Which class it is.
58    #[must_use]
59    pub const fn kind(self) -> Kind {
60        self.kind
61    }
62
63    /// An instance of it, which is what calling the class does.
64    #[must_use]
65    pub fn instance(self, args: Vec<Object>) -> Object {
66        Object::native(Exception::new(self.kind, args))
67    }
68}
69
70impl Native for Class {
71    /// The type of a class is `type`, the same as for every other class.
72    fn type_name(&self) -> &str {
73        "type"
74    }
75
76    fn repr(&self) -> String {
77        format!("<class '{}'>", self.kind.name())
78    }
79
80    fn as_any(&self) -> &dyn Any {
81        self
82    }
83}
84
85/// An exception instance.
86///
87/// Made by calling a [`Class`], and after that it is an ordinary value until
88/// something raises it.
89#[derive(Debug)]
90pub struct Exception {
91    kind: Kind,
92    args: Box<[Object]>,
93    /// What `raise this from that` put there.
94    ///
95    /// A cell because `raise x from y` sets it on an instance that already
96    /// exists and may already be bound to a name, which is what CPython does
97    /// too.
98    cause: RefCell<Option<Object>>,
99    /// What was being handled when this was raised, which is `__context__`.
100    ///
101    /// Nobody writes this. The runtime sets it whenever an exception is raised
102    /// inside an `except` clause, which is how a mistake in a handler prints
103    /// with the exception it was handling above it rather than on its own.
104    context: RefCell<Option<Object>>,
105    /// Whether to print the context, which is `__suppress_context__`.
106    ///
107    /// A written `from` sets this, including `raise x from None`. That is the
108    /// whole of what `from None` means: the context is still recorded and is
109    /// still readable, and the traceback stops printing it.
110    suppress: Cell<bool>,
111}
112
113impl Exception {
114    /// An instance of this class with these arguments.
115    #[must_use]
116    pub fn new(kind: Kind, args: Vec<Object>) -> Self {
117        Exception {
118            kind,
119            args: args.into_boxed_slice(),
120            cause: RefCell::new(None),
121            context: RefCell::new(None),
122            suppress: Cell::new(false),
123        }
124    }
125
126    /// Which class it is an instance of.
127    #[must_use]
128    pub const fn kind(&self) -> Kind {
129        self.kind
130    }
131
132    /// What it was constructed with, which is `e.args` and is what everything
133    /// it prints is made out of.
134    #[must_use]
135    pub fn args(&self) -> &[Object] {
136        &self.args
137    }
138
139    /// What it was raised from, if it was raised from anything.
140    ///
141    /// Cloned out rather than borrowed, because the cell it lives in cannot
142    /// stay borrowed across the walk up a chain of them.
143    #[must_use]
144    pub fn cause(&self) -> Option<Object> {
145        self.cause.borrow().clone()
146    }
147
148    /// Record what this was raised from, which is the `from` in a `raise`.
149    ///
150    /// Writing a `from` at all is what suppresses the context, so `raise x from
151    /// None` says "this one, and do not print whatever I happened to be
152    /// handling", which is the only way to write that sentence.
153    pub fn raised_from(&self, cause: Option<Object>) {
154        *self.cause.borrow_mut() = cause;
155        self.suppress.set(true);
156    }
157
158    /// What was being handled when this was raised, if anything was.
159    ///
160    /// Cloned out for the same reason [`Exception::cause`] is: the cell cannot
161    /// stay borrowed across the walk up a chain of them.
162    #[must_use]
163    pub fn context(&self) -> Option<Object> {
164        self.context.borrow().clone()
165    }
166
167    /// Whether a traceback should stop before printing the context.
168    #[must_use]
169    pub fn suppresses_context(&self) -> bool {
170        self.suppress.get()
171    }
172
173    /// What `str(e)` says, which is the half of a traceback's last line after
174    /// the colon.
175    ///
176    /// Three shapes and one exception to them. No arguments says nothing at
177    /// all, one argument is that argument, and more than one is the tuple of
178    /// them. `KeyError` is the one that prints its single argument the way
179    /// `repr` would, which is what makes a missing key of `''` visible.
180    #[must_use]
181    pub fn message(&self) -> String {
182        match &*self.args {
183            [] => String::new(),
184            [only] if self.kind == Kind::KeyError => only.repr(),
185            [only] => only.display(),
186            many => Object::tuple(many.to_vec()).repr(),
187        }
188    }
189}
190
191impl Native for Exception {
192    fn type_name(&self) -> &str {
193        self.kind.name()
194    }
195
196    /// The class name and the arguments, which is what reads back as the call
197    /// that would make it again.
198    fn repr(&self) -> String {
199        let args: Vec<String> = self.args.iter().map(Object::repr).collect();
200        format!("{}({})", self.kind.name(), args.join(", "))
201    }
202
203    fn display(&self) -> String {
204        self.message()
205    }
206
207    /// Every exception is true, including the ones with no arguments, which is
208    /// worth saying because an empty tuple is not.
209    fn truthy(&self) -> bool {
210        true
211    }
212
213    fn as_any(&self) -> &dyn Any {
214        self
215    }
216}
217
218/// The instance a value stands for where an exception is wanted.
219///
220/// An instance stands for itself, sharing rather than copying, because the
221/// object raised is the object caught. A class stands for a fresh instance of
222/// itself with no arguments, which is what makes `raise ValueError` and
223/// `raise ValueError()` the same statement. Anything else stands for nothing.
224#[must_use]
225pub fn instance_of(value: &Object) -> Option<Object> {
226    if value.exception().is_some() {
227        return Some(value.clone());
228    }
229    let class = value.downcast::<Class>()?;
230    Some(class.instance(Vec::new()))
231}
232
233/// Whether an `except` clause catches this exception.
234///
235/// The clause names a class or a tuple of them, and a class catches an
236/// exception that is an instance of it or of anything below it, which is the
237/// walk [`Kind::derives_from`] does. A tuple catches whatever any of its
238/// members catches, and only one deep: CPython used to allow a tuple inside a
239/// tuple and stopped, so a nested one is the same mistake as writing a number.
240///
241/// # Errors
242///
243/// A `TypeError` when the clause names something that is not an exception
244/// class, which is a mistake in the handler rather than in what it was trying
245/// to catch.
246pub fn matches(raised: &Exception, test: &Object) -> Result<bool> {
247    if let Object::Tuple(members) = test {
248        for member in members.iter() {
249            if caught_by(raised, member)? {
250                return Ok(true);
251            }
252        }
253        return Ok(false);
254    }
255    caught_by(raised, test)
256}
257
258/// One class of an `except` clause, which is the whole of it unless it is a
259/// tuple.
260fn caught_by(raised: &Exception, test: &Object) -> Result<bool> {
261    let Some(class) = test.downcast::<Class>() else {
262        return Err(Error::type_error(
263            "catching classes that do not inherit from BaseException is not allowed",
264        ));
265    };
266    Ok(raised.kind().derives_from(class.kind()))
267}
268
269/// Every builtin exception class, bound to its name.
270///
271/// Built once per run rather than per lookup, so that `ValueError is
272/// ValueError` is true the way it is in CPython.
273#[must_use]
274pub fn classes() -> Vec<(&'static str, Object)> {
275    Kind::ALL
276        .iter()
277        .map(|&kind| (kind.name(), Object::native(Class::new(kind))))
278        .collect()
279}
280
281/// What an exception that reached the top of the program does to the process.
282#[derive(Debug, Clone, PartialEq, Eq)]
283pub enum Exit {
284    /// Print this on standard error and stop unsuccessfully, which is what
285    /// every exception but one does.
286    Report(String),
287    /// Stop with this status and print nothing.
288    ///
289    /// Only `SystemExit` asks for this, and only when what it was given is a
290    /// number. The number is truncated to a byte because that is all a process
291    /// status has room for, which is why `SystemExit(256)` is a success and
292    /// `SystemExit(-1)` is a failure.
293    Status(u8),
294}
295
296/// What to do about an exception nothing caught.
297///
298/// `SystemExit` is the one class that is asking for something rather than
299/// reporting something, which is why it is the only one this has to look at.
300#[must_use]
301pub fn uncaught(error: &Error) -> Exit {
302    if error.kind != Kind::SystemExit {
303        return Exit::Report(error.to_string());
304    }
305    // `SystemExit.code` is the argument it was given, or nothing when it was
306    // given none, or all of them when it was given several.
307    let code = match error
308        .value()
309        .and_then(Object::exception)
310        .map(Exception::args)
311    {
312        None | Some([]) => Object::None,
313        Some([only]) => only.clone(),
314        Some(many) => Object::tuple(many.to_vec()),
315    };
316    match code {
317        Object::None => Exit::Status(0),
318        // A bool is an int here the way it is everywhere else, so
319        // `SystemExit(True)` is a failure and `SystemExit(False)` is not.
320        Object::Bool(value) => Exit::Status(u8::from(value)),
321        // A number too big for a machine word is the one CPython cannot
322        // convert either, and 255 is what it stops with when it cannot.
323        Object::Int(value) => Exit::Status(value.to_i64().map_or(255, status)),
324        // Anything else is a message, which goes out and fails.
325        other => Exit::Report(other.display()),
326    }
327}
328
329/// A process status from a number, the way a shell sees one.
330///
331/// `rem_euclid` rather than a cast so that a negative status wraps the way the
332/// operating system wraps it: `-1` is the 255 that `echo $?` prints.
333fn status(code: i64) -> u8 {
334    u8::try_from(code.rem_euclid(256)).unwrap_or(255)
335}
336
337/// Record that `raised` happened while `handled` was being handled.
338///
339/// This is what puts the exception a handler was working on above the one the
340/// handler went on to raise. Nothing a program writes reaches it: `__context__`
341/// is set by the runtime at the moment of the raise, and `raise x from y` only
342/// decides whether it is printed.
343///
344/// Two things it will not do, both of which are CPython's rules and both of
345/// which exist to keep the chain a chain. A bare `raise` inside a handler
346/// re-raises the exception being handled, and an exception is not raised while
347/// handling itself, so that one is left alone. And an exception that is already
348/// somewhere above `raised` in the chain has its link cut before the new one is
349/// made, because a ring here is a traceback that never finishes printing.
350pub fn raised_while_handling(raised: &Object, handled: &Object) {
351    let (Some(new), Some(old)) = (raised.exception(), handled.exception()) else {
352        return;
353    };
354    if std::ptr::eq(new, old) {
355        return;
356    }
357    // Terminates because this is the only thing that ever writes a context and
358    // it refuses to close a ring, so what it is walking is a chain.
359    let mut at = handled.clone();
360    loop {
361        let next = {
362            let Some(exception) = at.exception() else {
363                break;
364            };
365            let Some(context) = exception.context() else {
366                break;
367            };
368            if context
369                .exception()
370                .is_some_and(|link| std::ptr::eq(link, new))
371            {
372                *exception.context.borrow_mut() = None;
373                break;
374            }
375            context
376        };
377        at = next;
378    }
379    *new.context.borrow_mut() = Some(handled.clone());
380}
381
382/// The same exception, put back on its way out.
383///
384/// Not [`raise`], although it fails the same way. What is put back was raised
385/// once already and settled then what it was raised while handling, so none of
386/// that is settled again: an `except` chain that matched nothing and the end of
387/// a `finally` an exception reached are both the middle of one exception
388/// leaving rather than the start of another.
389///
390/// The register this reads is one the interpreter filled at a handler, so what
391/// is in it is always an exception. The other answer is there because
392/// hand-written bytecode could put anything anywhere, and a `TypeError` is a
393/// better thing to say about that than a panic.
394#[must_use]
395pub fn reraise(exc: &Object) -> Error {
396    let Some(exception) = exc.exception() else {
397        return Error::type_error("exceptions must derive from BaseException");
398    };
399    Error::new(exception.kind(), exception.message()).with_value(exc.clone())
400}
401
402/// What a `raise` statement raises.
403///
404/// The whole of the statement's meaning, which is worth having in one function
405/// away from the interpreter because none of it depends on the interpreter:
406/// what is raised is a value, what it is raised from is a value, and the answer
407/// is an [`Error`] either way, including when the answer is that the program
408/// raised something that is not an exception.
409#[must_use]
410pub fn raise(exc: Option<&Object>, cause: Option<&Object>) -> Error {
411    let Some(exc) = exc else {
412        // A bare `raise` re-raises whatever is being handled, and nothing can
413        // be being handled until there is an `except` to handle it in. Until
414        // then this is the only thing a bare `raise` can mean.
415        return Error::new(Kind::RuntimeError, "No active exception to reraise");
416    };
417    let Some(raised) = instance_of(exc) else {
418        return Error::type_error("exceptions must derive from BaseException");
419    };
420    // No `from` clause and `from None` both end up with nothing to record. They
421    // are not the same statement, which is what the check below is about, but
422    // they have the same cause and it is nothing.
423    let from = match cause {
424        None | Some(Object::None) => None,
425        Some(cause) => match instance_of(cause) {
426            Some(from) => Some(from),
427            None => {
428                return Error::type_error("exception causes must derive from BaseException");
429            }
430        },
431    };
432
433    let error = {
434        let Some(exception) = raised.exception() else {
435            unreachable!("what instance_of gives back is an exception or nothing")
436        };
437        // Only a written `from` touches the cause. `raise e` leaves whatever
438        // the instance already had, which matters because it can be re-raising
439        // one that was raised from something once already, and `from None` is
440        // written precisely to take that away.
441        if cause.is_some() {
442            exception.raised_from(from);
443        }
444        Error::new(exception.kind(), exception.message())
445    };
446    error.with_value(raised)
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452
453    fn exception(kind: Kind, args: Vec<Object>) -> Exception {
454        Exception::new(kind, args)
455    }
456
457    #[test]
458    fn a_class_prints_the_way_a_class_does_and_an_instance_the_way_a_call_does() {
459        assert_eq!(Class::new(Kind::ValueError).repr(), "<class 'ValueError'>");
460        assert_eq!(Class::new(Kind::ValueError).type_name(), "type");
461        assert_eq!(
462            exception(Kind::ValueError, Vec::new()).repr(),
463            "ValueError()"
464        );
465        assert_eq!(
466            exception(Kind::ValueError, vec![Object::str("x")]).repr(),
467            "ValueError('x')"
468        );
469        assert_eq!(
470            exception(Kind::ValueError, vec![Object::int(1), Object::int(2)]).repr(),
471            "ValueError(1, 2)"
472        );
473    }
474
475    /// `str` and `repr` differ for an exception the way they do for a string,
476    /// and for the same reason: one is for reading and one is for reading back.
477    #[test]
478    fn what_an_exception_says_is_its_arguments_rather_than_its_repr() {
479        assert_eq!(exception(Kind::ValueError, Vec::new()).message(), "");
480        assert_eq!(
481            exception(Kind::ValueError, vec![Object::str("boom")]).message(),
482            "boom"
483        );
484        assert_eq!(
485            exception(Kind::ValueError, vec![Object::int(1), Object::int(2)]).message(),
486            "(1, 2)"
487        );
488    }
489
490    /// A `KeyError` whose key is a string prints the quotes, because the key
491    /// `''` and the key `' '` are different keys and neither prints as
492    /// anything without them.
493    #[test]
494    fn a_key_error_says_its_key_the_way_repr_would() {
495        assert_eq!(
496            exception(Kind::KeyError, vec![Object::str("k")]).message(),
497            "'k'"
498        );
499        assert_eq!(
500            exception(Kind::KeyError, vec![Object::str("")]).message(),
501            "''"
502        );
503        // More than one argument is the tuple, for a `KeyError` like any other.
504        assert_eq!(
505            exception(Kind::KeyError, vec![Object::int(1), Object::int(2)]).message(),
506            "(1, 2)"
507        );
508    }
509
510    /// `except ArithmeticError` catches a `ZeroDivisionError` for the same
511    /// reason `except ZeroDivisionError` does, which is that a class catches
512    /// everything below it as well as itself.
513    #[test]
514    fn a_clause_catches_its_class_and_everything_under_it() {
515        let raised = exception(Kind::ZeroDivisionError, Vec::new());
516        for kind in [
517            Kind::ZeroDivisionError,
518            Kind::ArithmeticError,
519            Kind::Exception,
520            Kind::BaseException,
521        ] {
522            let test = Object::native(Class::new(kind));
523            assert!(
524                matches(&raised, &test).expect("a class is a clause"),
525                "{kind}"
526            );
527        }
528        let test = Object::native(Class::new(Kind::ValueError));
529        assert!(!matches(&raised, &test).expect("a class is a clause"));
530    }
531
532    /// `except (A, B)` catches whatever either of them catches, and only one
533    /// deep, because CPython used to allow a tuple inside a tuple and stopped.
534    #[test]
535    fn a_tuple_catches_what_any_of_its_members_catches() {
536        let raised = exception(Kind::ValueError, Vec::new());
537        let class = |kind| Object::native(Class::new(kind));
538        let test = Object::tuple(vec![class(Kind::KeyError), class(Kind::ValueError)]);
539        assert!(matches(&raised, &test).expect("a tuple is a clause"));
540
541        let test = Object::tuple(vec![class(Kind::KeyError), class(Kind::TypeError)]);
542        assert!(!matches(&raised, &test).expect("a tuple is a clause"));
543
544        // Empty, which is a clause that catches nothing rather than a mistake.
545        assert!(!matches(&raised, &Object::tuple(Vec::new())).expect("a tuple is a clause"));
546
547        let nested = Object::tuple(vec![Object::tuple(vec![class(Kind::ValueError)])]);
548        assert!(matches(&raised, &nested).is_err());
549    }
550
551    /// The mistake is in the handler rather than in what it was trying to
552    /// catch, so it is a `TypeError` and not the exception that was raised.
553    #[test]
554    fn a_clause_that_names_something_that_is_not_a_class_says_so() {
555        let raised = exception(Kind::ValueError, Vec::new());
556        let error = matches(&raised, &Object::int(5)).expect_err("a number is not a clause");
557        assert_eq!(
558            error.to_string(),
559            "TypeError: catching classes that do not inherit from BaseException is not allowed"
560        );
561        // An instance is not a class either, which is the mistake of writing
562        // `except e` where `e` is what a previous handler bound.
563        let instance = Object::native(exception(Kind::ValueError, Vec::new()));
564        assert!(matches(&raised, &instance).is_err());
565    }
566
567    #[test]
568    fn a_class_stands_for_an_instance_of_it_and_a_number_stands_for_nothing() {
569        let class = Object::native(Class::new(Kind::ValueError));
570        let made = instance_of(&class).expect("a class is something to raise");
571        assert_eq!(made.repr(), "ValueError()");
572        assert!(instance_of(&Object::int(5)).is_none());
573        assert!(instance_of(&Object::None).is_none());
574    }
575
576    /// The instance a `raise` is handed is the instance it raises, rather than
577    /// a copy that would fail an `is` against the original.
578    #[test]
579    fn an_instance_stands_for_itself() {
580        let raised = Object::native(exception(Kind::ValueError, vec![Object::str("x")]));
581        let again = instance_of(&raised).expect("an instance is something to raise");
582        assert!(raised.is(&again));
583    }
584
585    #[test]
586    fn every_builtin_class_is_bound_to_its_own_name() {
587        let bound = classes();
588        assert_eq!(bound.len(), Kind::ALL.len());
589        for (name, value) in &bound {
590            let class = value.downcast::<Class>().expect("a class is bound");
591            assert_eq!(class.kind().name(), *name);
592        }
593    }
594
595    /// The class and the instance are both accepted, and the message is what
596    /// the last line of the traceback would say.
597    #[test]
598    fn raising_a_class_and_raising_an_instance_of_it_say_the_same_thing() {
599        let class = Object::native(Class::new(Kind::ValueError));
600        assert_eq!(raise(Some(&class), None).to_string(), "ValueError");
601        let instance = Object::native(exception(Kind::ValueError, vec![Object::str("boom")]));
602        assert_eq!(raise(Some(&instance), None).to_string(), "ValueError: boom");
603    }
604
605    #[test]
606    fn raising_something_that_is_not_an_exception_says_so() {
607        assert_eq!(
608            raise(Some(&Object::int(5)), None).to_string(),
609            "TypeError: exceptions must derive from BaseException"
610        );
611        let cause = Object::int(5);
612        let raised = Object::native(exception(Kind::ValueError, Vec::new()));
613        assert_eq!(
614            raise(Some(&raised), Some(&cause)).to_string(),
615            "TypeError: exception causes must derive from BaseException"
616        );
617    }
618
619    /// A bare `raise` needs an exception to be being handled, and until there
620    /// is an `except` there never is one.
621    #[test]
622    fn a_bare_raise_has_nothing_to_re_raise() {
623        assert_eq!(
624            raise(None, None).to_string(),
625            "RuntimeError: No active exception to reraise"
626        );
627    }
628
629    /// The chain prints oldest first, which is the order it happened in, and
630    /// `from None` takes it away again.
631    #[test]
632    fn a_cause_prints_above_the_exception_it_caused() {
633        let cause = Object::native(exception(Kind::KeyError, vec![Object::str("k")]));
634        let raised = Object::native(exception(Kind::ValueError, vec![Object::str("boom")]));
635        assert_eq!(
636            raise(Some(&raised), Some(&cause)).to_string(),
637            "KeyError: 'k'\n\nThe above exception was the direct cause of the \
638             following exception:\n\nValueError: boom"
639        );
640        assert_eq!(
641            raise(Some(&raised), Some(&Object::None)).to_string(),
642            "ValueError: boom"
643        );
644    }
645
646    /// Everything but a `SystemExit` is something to report, including the
647    /// ones the runtime raised itself and so has no instance for.
648    #[test]
649    fn an_uncaught_exception_is_reported() {
650        assert_eq!(
651            uncaught(&Error::zero_division("division by zero")),
652            Exit::Report("ZeroDivisionError: division by zero".to_owned())
653        );
654        let raised = Object::native(exception(Kind::ValueError, vec![Object::str("boom")]));
655        assert_eq!(
656            uncaught(&raise(Some(&raised), None)),
657            Exit::Report("ValueError: boom".to_owned())
658        );
659    }
660
661    /// A `SystemExit` given a number is a status, and a status is a byte, so
662    /// 256 comes out a success and -1 comes out the 255 a shell prints.
663    #[test]
664    fn a_system_exit_given_a_number_is_that_status() {
665        let status = |args: Vec<Object>| {
666            let raised = Object::native(exception(Kind::SystemExit, args));
667            uncaught(&raise(Some(&raised), None))
668        };
669        assert_eq!(status(Vec::new()), Exit::Status(0));
670        assert_eq!(status(vec![Object::None]), Exit::Status(0));
671        assert_eq!(status(vec![Object::int(3)]), Exit::Status(3));
672        assert_eq!(status(vec![Object::int(256)]), Exit::Status(0));
673        assert_eq!(status(vec![Object::int(-1)]), Exit::Status(255));
674        // A bool is an int, so one of them is a failure and the other is not.
675        assert_eq!(status(vec![Object::Bool(true)]), Exit::Status(1));
676        assert_eq!(status(vec![Object::Bool(false)]), Exit::Status(0));
677    }
678
679    /// Anything that is not a number is a message, and several arguments are
680    /// the tuple of them, which is what `SystemExit.code` is in that case.
681    #[test]
682    fn a_system_exit_given_anything_else_is_a_message() {
683        let raised = Object::native(exception(Kind::SystemExit, vec![Object::str("no good")]));
684        assert_eq!(
685            uncaught(&raise(Some(&raised), None)),
686            Exit::Report("no good".to_owned())
687        );
688        let pair = Object::native(exception(
689            Kind::SystemExit,
690            vec![Object::str("a"), Object::str("b")],
691        ));
692        assert_eq!(
693            uncaught(&raise(Some(&pair), None)),
694            Exit::Report("('a', 'b')".to_owned())
695        );
696    }
697
698    /// The context is the exception the handler was already working on, and it
699    /// prints above the new one under its own sentence rather than a cause's.
700    #[test]
701    fn what_was_being_handled_prints_above_what_was_raised_while_handling_it() {
702        let handled = Object::native(exception(Kind::ValueError, vec![Object::str("a")]));
703        let raised = Object::native(exception(Kind::KeyError, vec![Object::str("b")]));
704        raised_while_handling(&raised, &handled);
705        assert_eq!(
706            raise(Some(&raised), None).to_string(),
707            "ValueError: a\n\nDuring handling of the above exception, another \
708             exception occurred:\n\nKeyError: 'b'"
709        );
710    }
711
712    /// A `from` clause wins over a context, and writing one at all is what
713    /// stops the context being printed, which is the whole of what `from None`
714    /// means.
715    #[test]
716    fn a_cause_is_printed_instead_of_a_context_and_from_none_prints_neither() {
717        let handled = Object::native(exception(Kind::ValueError, vec![Object::str("a")]));
718        let cause = Object::native(exception(Kind::IndexError, vec![Object::str("i")]));
719        let raised = Object::native(exception(Kind::KeyError, vec![Object::str("b")]));
720        raised_while_handling(&raised, &handled);
721        assert_eq!(
722            raise(Some(&raised), Some(&cause)).to_string(),
723            "IndexError: i\n\nThe above exception was the direct cause of the \
724             following exception:\n\nKeyError: 'b'"
725        );
726        // The context is still there and still readable. What `from None` takes
727        // away is the printing of it.
728        assert_eq!(
729            raise(Some(&raised), Some(&Object::None)).to_string(),
730            "KeyError: 'b'"
731        );
732    }
733
734    /// Nothing is raised while handling itself, which is what a bare `raise`
735    /// and a `raise` of what a clause just caught both are.
736    #[test]
737    fn an_exception_is_not_the_context_of_itself() {
738        let raised = Object::native(exception(Kind::ValueError, vec![Object::str("a")]));
739        raised_while_handling(&raised, &raised);
740        assert_eq!(raise(Some(&raised), None).to_string(), "ValueError: a");
741    }
742
743    /// An exception put back over the top of something that already records it
744    /// would close a ring, so the older link is cut on the way past, however
745    /// far up it is.
746    #[test]
747    fn making_a_context_cuts_whatever_link_would_close_a_ring() {
748        let a = Object::native(exception(Kind::ValueError, vec![Object::str("a")]));
749        let b = Object::native(exception(Kind::KeyError, vec![Object::str("b")]));
750        let c = Object::native(exception(Kind::IndexError, vec![Object::str("c")]));
751        raised_while_handling(&b, &a);
752        raised_while_handling(&c, &b);
753        // `c` has `b` which has `a`, and now `a` is raised while `c` is being
754        // handled, so `b` loses its `a` and the chain is `b`, `c`, `a`.
755        raised_while_handling(&a, &c);
756        assert_eq!(
757            raise(Some(&a), None).to_string(),
758            "KeyError: 'b'\n\nDuring handling of the above exception, another \
759             exception occurred:\n\nIndexError: c\n\nDuring handling of the \
760             above exception, another exception occurred:\n\nValueError: a"
761        );
762    }
763
764    /// Putting an exception back says the same thing raising it does, and
765    /// leaves what it was raised while handling alone rather than settling it
766    /// again.
767    #[test]
768    fn a_reraise_says_the_same_thing_and_settles_nothing_again() {
769        let handled = Object::native(exception(Kind::ValueError, vec![Object::str("a")]));
770        let raised = Object::native(exception(Kind::KeyError, vec![Object::str("b")]));
771        raised_while_handling(&raised, &handled);
772        let put_back = reraise(&raised);
773        assert!(put_back.instance().is(&raised));
774        assert_eq!(
775            put_back.to_string(),
776            "ValueError: a\n\nDuring handling of the above exception, another \
777             exception occurred:\n\nKeyError: 'b'"
778        );
779    }
780
781    /// `raise e from e` is a ring, and a printer that followed it would not
782    /// come back. CPython prints it once, because the exception it is printing
783    /// counts as already printed before it looks for a cause.
784    #[test]
785    fn an_exception_raised_from_itself_prints_once() {
786        let raised = Object::native(exception(Kind::ValueError, vec![Object::str("x")]));
787        assert_eq!(
788            raise(Some(&raised), Some(&raised)).to_string(),
789            "ValueError: x"
790        );
791    }
792}