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}