diff --git a/extra_tests/snippets/builtin_exceptions.py b/extra_tests/snippets/builtin_exceptions.py index 902393ce5b8..5f92f044eee 100644 --- a/extra_tests/snippets/builtin_exceptions.py +++ b/extra_tests/snippets/builtin_exceptions.py @@ -1,5 +1,6 @@ import builtins import platform +import pickle import sys def exceptions_eq(e1, e2): @@ -273,6 +274,14 @@ class SubError(MyError): assert w.filename == None assert w.filename2 == None assert str(w) == "" +x = pickle.loads(pickle.dumps(w, 4)) +assert type(w) == type(x) +assert x.errno == None +assert not sys.platform.startswith("win") or x.winerror == None +assert x.strerror == None +assert x.filename == None +assert x.filename2 == None +assert str(x) == "" w = OSError(0) assert w.errno == None @@ -281,6 +290,14 @@ class SubError(MyError): assert w.filename == None assert w.filename2 == None assert str(w) == "0" +x = pickle.loads(pickle.dumps(w, 4)) +assert type(w) == type(x) +assert x.errno == None +assert not sys.platform.startswith("win") or x.winerror == None +assert x.strerror == None +assert x.filename == None +assert x.filename2 == None +assert str(x) == "0" w = OSError('foo') assert w.errno == None @@ -289,6 +306,14 @@ class SubError(MyError): assert w.filename == None assert w.filename2 == None assert str(w) == "foo" +x = pickle.loads(pickle.dumps(w, 4)) +assert type(w) == type(x) +assert x.errno == None +assert not sys.platform.startswith("win") or x.winerror == None +assert x.strerror == None +assert x.filename == None +assert x.filename2 == None +assert str(x) == "foo" w = OSError('a', 'b', 'c', 'd', 'e', 'f') assert w.errno == None @@ -297,6 +322,14 @@ class SubError(MyError): assert w.filename == None assert w.filename2 == None assert str(w) == "('a', 'b', 'c', 'd', 'e', 'f')" +x = pickle.loads(pickle.dumps(w, 4)) +assert type(w) == type(x) +assert x.errno == None +assert not sys.platform.startswith("win") or x.winerror == None +assert x.strerror == None +assert x.filename == None +assert x.filename2 == None +assert str(x) == "('a', 'b', 'c', 'd', 'e', 'f')" # Custom `__new__` and `__init__`: assert ImportError.__init__.__qualname__ == 'ImportError.__init__' diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index e5629fa41a6..4626a4dfd44 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1,4 +1,5 @@ use self::types::{PyBaseException, PyBaseExceptionRef}; +use crate::builtins::tuple::IntoPyTuple; use crate::common::lock::PyRwLock; use crate::{ builtins::{ @@ -773,6 +774,7 @@ impl ExceptionZoo { // second exception filename "filename2" => ctx.none(), "__str__" => ctx.new_method("__str__", excs.os_error, os_error_str), + "__reduce__" => ctx.new_method("__reduce__", excs.os_error, os_error_reduce), }); // TODO: this isn't really accurate #[cfg(windows)] @@ -907,6 +909,42 @@ fn os_error_str(exc: PyBaseExceptionRef, vm: &VirtualMachine) -> PyResult PyTupleRef { + let args = exc.args(); + let obj = exc.as_object().to_owned(); + let mut result: Vec = vec![obj.class().clone().into()]; + + if args.len() >= 2 && args.len() <= 5 { + // SAFETY: len() == 2 is checked so get_arg 1 or 2 won't panic + let errno = exc.get_arg(0).unwrap(); + let msg = exc.get_arg(1).unwrap(); + + if let Ok(filename) = obj.get_attr("filename", vm) { + if !vm.is_none(&filename) { + let mut args_reduced: Vec = vec![errno, msg, filename]; + + if let Ok(filename2) = obj.get_attr("filename2", vm) { + if !vm.is_none(&filename2) { + args_reduced.push(filename2); + } + } + result.push(args_reduced.into_pytuple(vm).into()); + } else { + result.push(vm.new_tuple((errno, msg)).into()); + } + } else { + result.push(vm.new_tuple((errno, msg)).into()); + } + } else { + result.push(args.into()); + } + + if let Some(dict) = obj.dict().filter(|x| !x.is_empty()) { + result.push(dict.into()); + } + result.into_pytuple(vm) +} + fn system_exit_code(exc: PyBaseExceptionRef) -> Option { exc.args.read().first().map(|code| { match_class!(match code {