mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-29 03:04:38 +08:00
Match CPython error type for non-ASCII struct format arguments (#7681)
* Match CPython error type for non-ASCII struct format arguments
Struct() raised the wrong exception type when the format argument
contained non-ASCII characters:
- str input with non-ASCII char: RustPython raised UnicodeDecodeError
with an empty message; CPython raises UnicodeEncodeError as if
format.encode('ascii') had been called directly.
- bytes input with non-ASCII byte: same wrong UnicodeDecodeError;
CPython passes the bytes through to the format parser, which then
errors with struct.error("bad char in struct format").
Restructure IntoStructFormatBytes::try_from_object to:
- raise UnicodeEncodeError("ascii", s, start, start+1, "ordinal not
in range(128)") for non-ASCII str, with start computed as the
first non-ASCII code point position (matching CPython's natural
encoding-error format);
- raise struct.error("bad char in struct format") for non-ASCII bytes,
produced via the existing new_struct_error helper.
Probed byte-identical with CPython 3.14.4 for both cases. Full
test.test_struct (43 tests) passes with no regressions. Sanity-tested
all standard format/pack/unpack/calcsize call shapes remain unchanged.
* Add regression test for non-ASCII format string error types
* Use raise AssertionError instead of assert False (B011)
This commit is contained in:
1 parent
6ab1f806ba
commit
be43bb6dbf
2 files changed
+46
-8
No files matched your search
@@ -26,20 +26,42 @@ pub(crate) mod _struct {
|
||||
|
||||
impl TryFromObject for IntoStructFormatBytes {
|
||||
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
|
||||
// CPython turns str to bytes but we do reversed way here
|
||||
// The only performance difference is this transition cost
|
||||
// CPython turns str to bytes (via str.encode('ascii')) but we keep str.
|
||||
// The error reporting for non-ASCII input still matches CPython:
|
||||
// - str input with non-ASCII char: UnicodeEncodeError, the same exception
|
||||
// str.encode('ascii') would produce.
|
||||
// - bytes input with non-ASCII byte: struct.error("bad char in struct format"),
|
||||
// matching CPython where bytes are passed through to the format parser.
|
||||
let fmt = match_class!(match obj {
|
||||
s @ PyStr => s.isascii().then_some(s),
|
||||
b @ PyBytes => ascii::AsciiStr::from_ascii(&b)
|
||||
.ok()
|
||||
.map(|s| vm.ctx.new_str(s)),
|
||||
s @ PyStr => {
|
||||
if !s.isascii() {
|
||||
let start = s
|
||||
.as_wtf8()
|
||||
.code_points()
|
||||
.position(|cp| !cp.to_char().is_some_and(|c| c.is_ascii()))
|
||||
.unwrap_or(0);
|
||||
return Err(vm.new_unicode_encode_error_real(
|
||||
vm.ctx.new_str("ascii"),
|
||||
s,
|
||||
start,
|
||||
start + 1,
|
||||
vm.ctx.new_str("ordinal not in range(128)"),
|
||||
));
|
||||
}
|
||||
s
|
||||
}
|
||||
b @ PyBytes => {
|
||||
let ascii_str = ascii::AsciiStr::from_ascii(&b).map_err(|_| {
|
||||
new_struct_error(vm, "bad char in struct format".to_owned())
|
||||
})?;
|
||||
vm.ctx.new_str(ascii_str)
|
||||
}
|
||||
other =>
|
||||
return Err(vm.new_type_error(format!(
|
||||
"Struct() argument 1 must be a str or bytes object, not {}",
|
||||
other.class().name()
|
||||
))),
|
||||
})
|
||||
.ok_or_else(|| vm.new_unicode_decode_error("Struct format must be a ascii string"))?;
|
||||
});
|
||||
Ok(Self(fmt))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,3 +76,19 @@ assert data == b"\0"
|
||||
|
||||
assert struct.error.__module__ == "struct"
|
||||
assert struct.error.__name__ == "error"
|
||||
|
||||
# Non-ASCII format string: error type matches CPython.
|
||||
# str → UnicodeEncodeError (encoding='ascii')
|
||||
# bytes → struct.error
|
||||
try:
|
||||
struct.Struct("\udc00")
|
||||
except UnicodeEncodeError as e:
|
||||
assert e.encoding == "ascii"
|
||||
else:
|
||||
raise AssertionError("expected UnicodeEncodeError")
|
||||
|
||||
with assert_raises(UnicodeEncodeError):
|
||||
struct.Struct("한")
|
||||
|
||||
with assert_raises(struct.error):
|
||||
struct.Struct(b"\xff")
|
||||
Reference in new issue
Block a user