From 9dce9bf0b97d066c001894228724d9a6dfbc1c2d Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Sun, 31 Oct 2021 16:28:52 -0400 Subject: [PATCH 1/2] correctly set __module__ and __name__ for builtin exceptions In CPython's PyErr_NewException[0], the dotted "module.name" passed in is split on that dot: the content before the dot is set as __module__ in the created exception class, and the content after it is set as __name__. This commit mirrors that behaviour, by introducing a `module` argument to `PyContext.new_class`: if Some("module_name") is passed, it will be set as `__module__`. All exception-creating uses of `new_class` are updated to pass in their module and class names separately. [0] https://github.com/python/cpython/blob/main/Python/errors.c#L1082-L1087 --- stdlib/src/binascii.rs | 6 ++++-- stdlib/src/socket.rs | 9 ++++++--- stdlib/src/termios.rs | 3 ++- vm/src/macros.rs | 2 +- vm/src/pyobject.rs | 14 ++++++++++++-- vm/src/stdlib/pystruct.rs | 3 ++- 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/stdlib/src/binascii.rs b/stdlib/src/binascii.rs index a56831d6f..4e3a53635 100644 --- a/stdlib/src/binascii.rs +++ b/stdlib/src/binascii.rs @@ -19,7 +19,8 @@ mod decl { BINASCII_ERROR .get_or_init(|| { vm.ctx.new_class( - "binascii.Error", + Some("binascii"), + "Error", &vm.ctx.exceptions.value_error, Default::default(), ) @@ -35,7 +36,8 @@ mod decl { BINASCII_INCOMPLTE .get_or_init(|| { vm.ctx.new_class( - "binascii.Incomplete", + Some("binascii"), + "Incomplete", &vm.ctx.exceptions.exception_type, Default::default(), ) diff --git a/stdlib/src/socket.rs b/stdlib/src/socket.rs index bdc4b8f5d..12e1cafd9 100644 --- a/stdlib/src/socket.rs +++ b/stdlib/src/socket.rs @@ -89,7 +89,8 @@ mod _socket { ERROR .get_or_init(|| { vm.ctx.new_class( - "socket.timeout", + Some("socket"), + "timeout", &vm.ctx.exceptions.os_error, Default::default(), ) @@ -104,7 +105,8 @@ mod _socket { ERROR .get_or_init(|| { vm.ctx.new_class( - "socket.herror", + Some("socket"), + "herror", &vm.ctx.exceptions.os_error, Default::default(), ) @@ -119,7 +121,8 @@ mod _socket { ERROR .get_or_init(|| { vm.ctx.new_class( - "socket.gaierror", + Some("socket"), + "gaierror", &vm.ctx.exceptions.os_error, Default::default(), ) diff --git a/stdlib/src/termios.rs b/stdlib/src/termios.rs index 14bd56388..97ccdac29 100644 --- a/stdlib/src/termios.rs +++ b/stdlib/src/termios.rs @@ -108,7 +108,8 @@ mod termios { TERMIOS_ERROR .get_or_init(|| { vm.ctx.new_class( - "termios.error", + Some("termios"), + "error", &vm.ctx.exceptions.os_error, Default::default(), ) diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 1c660c367..bf56b1d93 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -19,7 +19,7 @@ macro_rules! py_class { #[allow(unused_mut)] let mut slots = $crate::types::PyTypeSlots::from_flags($crate::types::PyTypeFlags::DEFAULT | $flags); $($crate::py_class!(@extract_slots($ctx, &mut slots, $name, $value));)* - let py_class = $ctx.new_class($class_name, $class_base, slots); + let py_class = $ctx.new_class(None, $class_name, $class_base, slots); $($crate::py_class!(@extract_attrs($ctx, &py_class, $name, $value));)* py_class } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 6a27be85f..d7cff5d60 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -221,11 +221,21 @@ impl PyContext { PyDict::new_ref(self) } - pub fn new_class(&self, name: &str, base: &PyTypeRef, slots: PyTypeSlots) -> PyTypeRef { + pub fn new_class( + &self, + module: Option<&str>, + name: &str, + base: &PyTypeRef, + slots: PyTypeSlots, + ) -> PyTypeRef { + let mut attrs = PyAttributes::default(); + if let Some(module) = module { + attrs.insert("__module__".to_string(), self.new_str(module).into()); + }; PyType::new_ref( name, vec![base.clone()], - Default::default(), + attrs, slots, self.types.type_type.clone(), ) diff --git a/vm/src/stdlib/pystruct.rs b/vm/src/stdlib/pystruct.rs index 3a6045cbd..7530a303c 100644 --- a/vm/src/stdlib/pystruct.rs +++ b/vm/src/stdlib/pystruct.rs @@ -968,7 +968,8 @@ pub(crate) mod _struct { STRUCT_ERROR .get_or_init(|| { vm.ctx.new_class( - "struct.error", + Some("struct"), + "error", &vm.ctx.exceptions.exception_type, Default::default(), ) From 42186caade009b15a40ce89c923370a11a947b65 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Mon, 1 Nov 2021 08:59:33 -0400 Subject: [PATCH 2/2] extra_tests: test stdlib exception __module__/__name__ --- extra_tests/snippets/stdlib_binascii.py | 6 ++++++ extra_tests/snippets/stdlib_socket.py | 8 ++++++++ extra_tests/snippets/stdlib_struct.py | 5 ++++- extra_tests/snippets/stdlib_termios.py | 13 +++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 extra_tests/snippets/stdlib_termios.py diff --git a/extra_tests/snippets/stdlib_binascii.py b/extra_tests/snippets/stdlib_binascii.py index c3b9fc698..666f67d51 100644 --- a/extra_tests/snippets/stdlib_binascii.py +++ b/extra_tests/snippets/stdlib_binascii.py @@ -50,3 +50,9 @@ assert_equal( "☢🐣 ᖇ𝓤𝕊тⓟ𝕐𝕥卄σ𝔫 ♬👣".encode(), ) +for exc, expected_name in [ + (binascii.Error, "Error"), + (binascii.Incomplete, "Incomplete"), +]: + assert exc.__module__ == "binascii" + assert exc.__name__ == expected_name diff --git a/extra_tests/snippets/stdlib_socket.py b/extra_tests/snippets/stdlib_socket.py index 51124537a..3230dbbfd 100644 --- a/extra_tests/snippets/stdlib_socket.py +++ b/extra_tests/snippets/stdlib_socket.py @@ -156,3 +156,11 @@ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener: with assert_raises(OSError): # TODO: check that it raises a socket.timeout # testing that it doesn't work with the timeout; that it stops blocking eventually connection.recv(len(MESSAGE_A)) + +for exc, expected_name in [ + (socket.gaierror, "gaierror"), + (socket.herror, "herror"), + (socket.timeout, "timeout"), +]: + assert exc.__module__ == "socket" + assert exc.__name__ == expected_name diff --git a/extra_tests/snippets/stdlib_struct.py b/extra_tests/snippets/stdlib_struct.py index 26a02a631..83154c810 100644 --- a/extra_tests/snippets/stdlib_struct.py +++ b/extra_tests/snippets/stdlib_struct.py @@ -70,4 +70,7 @@ data = struct.pack('?', True) assert data == b'\1' data = struct.pack('?', []) -assert data == b'\0' \ No newline at end of file +assert data == b'\0' + +assert struct.error.__module__ == "struct" +assert struct.error.__name__ == "error" diff --git a/extra_tests/snippets/stdlib_termios.py b/extra_tests/snippets/stdlib_termios.py new file mode 100644 index 000000000..e15078744 --- /dev/null +++ b/extra_tests/snippets/stdlib_termios.py @@ -0,0 +1,13 @@ +def _test_termios(): + # These tests are in a function so we can only run them if termios is available + assert termios.error.__module__ == "termios" + assert termios.error.__name__ == "error" + + +try: + import termios +except ImportError: + # Not all platforms have termios, noop + pass +else: + _test_termios()