Support Python 3.14 complex values in struct formats (#8829)

Accept F/D through the existing buffer packing implementation and restore
its complex round-trip test. Fix exact __complex__ results warning as
subclasses so valid conversions also work with warnings treated as errors.

Codex assisted with implementation, regression coverage, and review.

Tested: CPython 3.14.7 comparison; struct/memoryview/complex/cmath; workspace and C-API tests; Clippy; pytest; prek
Assisted-by: Codex:gpt-6-astra
This commit is contained in:
Kwak Byoung Min authored and GitHub committed 2026-09-26 22:57:24 +09:00
1 parent 00dfed8ff9
commit 5f18b9cd56
3 files changed
+68 -19

No files matched your search

+35 -18
View File
@@ -1,6 +1,6 @@
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyResult, TryFromObject, VirtualMachine,
builtins::{PyBaseExceptionRef, PyBytesRef, PyTuple, PyTupleRef, PyType, PyTypeRef},
builtins::{PyBaseExceptionRef, PyBytesRef, PyComplex, PyTuple, PyTupleRef, PyType, PyTypeRef},
common::{static_cell, str::wchar_t},
convert::ToPyObject,
exceptions,
@@ -780,7 +780,7 @@ struct PackFloatComplex(f32, f32);
struct PackDoubleComplex(f64, f64);
macro_rules! make_pack_complex {
($T:ty, $Elem:ty, $Bits:ty) => {
($T:ty, $Elem:ty, $Bits:ty, $fmt:literal) => {
impl Packable for $T {
fn pack<E: ByteOrder>(
vm: &VirtualMachine,
@@ -788,20 +788,37 @@ macro_rules! make_pack_complex {
arg: PyObjectRef,
data: &mut [u8],
) -> Result<(), PackError> {
let c = ArgIntoComplex::try_from_object(vm, arg)
.map_err(|_| {
PackError::new(
PackErrorKind::Type,
vm,
"required argument is not a complex",
)
})?
.into_complex();
let half = size_of::<$Elem>();
(c.re as $Elem).to_bits().pack_int::<E>(&mut data[..half]);
(c.im as $Elem)
.to_bits()
.pack_int::<E>(&mut data[half..half * 2]);
let c = if let Some(value) = arg.downcast_ref::<PyComplex>() {
value.to_complex()
} else {
ArgIntoComplex::try_from_object(vm, arg)
.map_err(|_| {
PackError::new(
PackErrorKind::Type,
vm,
"required argument is not a complex",
)
})?
.into_complex()
};
for (component, bytes) in [c.re, c.im]
.into_iter()
.zip(data.chunks_exact_mut(size_of::<$Elem>()))
{
let narrowed = component as $Elem;
// CPython uses native casts for matching-endian complex formats.
if E::convert(1u16) != 1 && narrowed.is_infinite() != component.is_infinite() {
return Err(PackError {
kind: PackErrorKind::Value,
exception: vm.new_overflow_error(concat!(
"float too large to pack with ",
$fmt,
" format"
)),
});
}
narrowed.to_bits().pack_int::<E>(bytes);
}
Ok(())
}
@@ -815,8 +832,8 @@ macro_rules! make_pack_complex {
};
}
make_pack_complex!(PackFloatComplex, f32, u32);
make_pack_complex!(PackDoubleComplex, f64, u64);
make_pack_complex!(PackFloatComplex, f32, u32, "f");
make_pack_complex!(PackDoubleComplex, f64, u64, "d");
impl Packable for f16 {
fn pack<E: ByteOrder>(
+3 -1
View File
@@ -94,7 +94,9 @@ impl PyObjectRef {
let result = method?.call((), vm)?;
let ret_class = result.class().to_owned();
if let Some(ret) = result.downcast_ref::<PyComplex>() {
if let Some(ret) = result.downcast_ref::<PyComplex>()
&& !result.class().is(vm.ctx.types.complex_type)
{
_warnings::warn(
vm.ctx.exceptions.deprecation_warning,
format!(
@@ -0,0 +1,30 @@
import struct
import sys
import warnings
from testutils import assert_raises
class ComplexLike:
def __complex__(self):
return 1 + 2j
class ComplexSubclass(complex):
def __complex__(self):
raise AssertionError("complex subclasses use their stored value")
with warnings.catch_warnings():
warnings.simplefilter("error")
for code in ("F", "D"):
expected = struct.pack(code, 1 + 2j)
assert struct.pack(code, ComplexLike()) == expected
assert struct.pack(code, ComplexSubclass(1, 2)) == expected
native = "<" if sys.byteorder == "little" else ">"
swapped = ">" if sys.byteorder == "little" else "<"
for value in (complex(1e300, 0), complex(0, 1e300)):
assert struct.pack(native + "F", value) == struct.pack("@F", value)
with assert_raises(OverflowError):
struct.pack(swapped + "F", value)