Allow SyntaxError.msg to be writable and reflected in string formatting (#6493)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: youknowone <69878+youknowone@users.noreply.github.com>
This commit is contained in:
Copilot
2025-12-25 09:08:18 +09:00
committed by GitHub
parent cbde5ce321
commit be9e44aafb
3 changed files with 43 additions and 12 deletions

View File

@@ -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

View File

@@ -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<PyObject
move |exc| exc.get_arg(idx)
}
fn syntax_error_set_msg(
exc: PyBaseExceptionRef,
value: PySetterValue,
vm: &VirtualMachine,
) -> 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<PyObjectRef> {
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("<filename str() failed>"))
});
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("<msg str() failed>")),
Err(_) => {
// Fallback to the base formatting if the msg attribute was deleted or attribute lookup fails for any reason.
return Py::<PyBaseException>::__str__(zelf, vm);
}
};
let msg_with_location_info: String = match (maybe_lineno, maybe_filename) {

View File

@@ -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