diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index aa7d99eef..d5c675fac 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -130,7 +130,8 @@ repository's structure: - `stdlib`: Standard library parts implemented in rust. - `src`: using the other subcrates to bring rustpython to life. - `wasm`: Binary crate and resources for WebAssembly build -- `extra_tests`: extra integration test snippets as a supplement to `Lib/test` +- `extra_tests`: extra integration test snippets as a supplement to `Lib/test`. + Add new RustPython-only regression tests here; do not place new tests under `Lib/test`. ## Understanding Internals diff --git a/crates/vm/src/exceptions.rs b/crates/vm/src/exceptions.rs index 3a932a5df..8d6ce6142 100644 --- a/crates/vm/src/exceptions.rs +++ b/crates/vm/src/exceptions.rs @@ -9,7 +9,7 @@ use crate::{ }, class::{PyClassImpl, StaticType}, convert::{ToPyException, ToPyObject}, - function::{ArgIterable, FuncArgs, IntoFuncArgs}, + function::{ArgIterable, FuncArgs, IntoFuncArgs, PySetterValue}, py_io::{self, Write}, stdlib::sys, suggestion::offer_suggestions, @@ -994,7 +994,12 @@ impl ExceptionZoo { extend_exception!(PyRecursionError, ctx, excs.recursion_error); extend_exception!(PySyntaxError, ctx, excs.syntax_error, { - "msg" => ctx.new_readonly_getset("msg", excs.syntax_error, make_arg_getter(0)), + "msg" => ctx.new_static_getset( + "msg", + excs.syntax_error, + make_arg_getter(0), + syntax_error_set_msg, + ), // TODO: members "filename" => ctx.none(), "lineno" => ctx.none(), @@ -1041,6 +1046,25 @@ fn make_arg_getter(idx: usize) -> impl Fn(PyBaseExceptionRef) -> Option PyResult<()> { + let mut args = exc.args.write(); + let mut new_args = args.as_slice().to_vec(); + // Ensure the message slot at index 0 always exists for SyntaxError.args. + if new_args.is_empty() { + new_args.push(vm.ctx.none()); + } + match value { + PySetterValue::Assign(value) => new_args[0] = value, + PySetterValue::Delete => new_args[0] = vm.ctx.none(), + } + *args = PyTuple::new_ref(new_args, &vm.ctx); + Ok(()) +} + fn system_exit_code(exc: PyBaseExceptionRef) -> Option { exc.args.read().first().map(|code| { match_class!(match code { @@ -2048,15 +2072,14 @@ pub(super) mod types { .unwrap_or_else(|_| vm.ctx.new_str("")) }); - let args = zelf.args(); - - let msg = if args.len() == 1 { - vm.exception_args_as_string(args, false) - .into_iter() - .exactly_one() - .unwrap() - } else { - return zelf.__str__(vm); + let msg = match zelf.as_object().get_attr("msg", vm) { + Ok(obj) => obj + .str(vm) + .unwrap_or_else(|_| vm.ctx.new_str("")), + Err(_) => { + // Fallback to the base formatting if the msg attribute was deleted or attribute lookup fails for any reason. + return Py::::__str__(zelf, vm); + } }; let msg_with_location_info: String = match (maybe_lineno, maybe_filename) { diff --git a/extra_tests/snippets/builtin_exceptions.py b/extra_tests/snippets/builtin_exceptions.py index 490f831f5..0af5cf05e 100644 --- a/extra_tests/snippets/builtin_exceptions.py +++ b/extra_tests/snippets/builtin_exceptions.py @@ -85,6 +85,13 @@ assert exc.lineno is None assert exc.offset is None assert exc.text is None +err = SyntaxError("bad bad", ("bad.py", 1, 2, "abcdefg")) +err.msg = "changed" +assert err.msg == "changed" +assert str(err) == "changed (bad.py, line 1)" +del err.msg +assert err.msg is None + # Regression to: # https://github.com/RustPython/RustPython/issues/2779