From c60f4a404b43e835f85c8ed7e51f63f1d9f36bf5 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 30 Jun 2024 11:16:48 +0300 Subject: [PATCH 01/10] gh-121249: Support _Complex types in the struct module --- Doc/library/struct.rst | 19 ++ Lib/ctypes/__init__.py | 2 + Lib/test/test_struct.py | 49 ++++ ...-07-10-08-13-34.gh-issue-121249.W9Gd09.rst | 3 + Modules/_struct.c | 210 ++++++++++++++++++ 5 files changed, 283 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-07-10-08-13-34.gh-issue-121249.W9Gd09.rst diff --git a/Doc/library/struct.rst b/Doc/library/struct.rst index a2c293443e23d3..e0fae2b85a90f8 100644 --- a/Doc/library/struct.rst +++ b/Doc/library/struct.rst @@ -267,12 +267,26 @@ platform-dependent. | ``P`` | :c:expr:`void \*` | integer | | \(5) | +--------+--------------------------+--------------------+----------------+------------+ +Additionally, if IEC 60559 compatible complex arithmetic (Annex G) is +supported, the following format characters are available: + ++--------+--------------------------+--------------------+----------------+------------+ +| Format | C Type | Python type | Standard size | Notes | ++========+==========================+====================+================+============+ +| ``E`` | :c:expr:`float complex` | complex | 8 | \(10) | ++--------+--------------------------+--------------------+----------------+------------+ +| ``C`` | :c:expr:`double complex` | complex | 16 | \(10) | ++--------+--------------------------+--------------------+----------------+------------+ + .. versionchanged:: 3.3 Added support for the ``'n'`` and ``'N'`` formats. .. versionchanged:: 3.6 Added support for the ``'e'`` format. +.. versionchanged:: 3.14 + Added support for ``'E'`` and ``'C'`` formats. + Notes: @@ -349,6 +363,11 @@ Notes: of bytes. As a special case, ``'0s'`` means a single, empty string (while ``'0c'`` means 0 characters). +(10) + For the ``'E'`` and ``'C'`` conversion codes, the packed representation uses + the IEEE 754 binary32 and binary64 format for components of the complex + number, regardless of the floating-point format used by the platform. + A format character may be preceded by an integral repeat count. For example, the format string ``'4h'`` means exactly the same as ``'hhhh'``. diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 721522caeeac92..9ef5d8817a2933 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -208,8 +208,10 @@ class c_longdouble(_SimpleCData): try: class c_double_complex(_SimpleCData): _type_ = "C" + _check_size(c_double_complex) class c_float_complex(_SimpleCData): _type_ = "E" + _check_size(c_float_complex) class c_longdouble_complex(_SimpleCData): _type_ = "F" except AttributeError: diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 5508cc3eec85c8..50bd020e32bc56 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -1,4 +1,5 @@ from collections import abc +from itertools import combinations import array import gc import math @@ -17,6 +18,15 @@ integer_codes = 'b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q', 'n', 'N' byteorders = '', '@', '=', '<', '>', '!' +INF = float('inf') +NAN = float('nan') + +try: + struct.pack('C', 1j) + have_c_complex = True +except struct.error: + have_c_complex = False + def iter_integer_formats(byteorders=byteorders): for code in integer_codes: for byteorder in byteorders: @@ -34,6 +44,34 @@ def bigendian_to_native(value): return string_reverse(value) class StructTest(unittest.TestCase): + # from Lib/test/test_complex.py + def assertFloatsAreIdentical(self, x, y): + """assert that floats x and y are identical, in the sense that: + (1) both x and y are nans, or + (2) both x and y are infinities, with the same sign, or + (3) both x and y are zeros, with the same sign, or + (4) x and y are both finite and nonzero, and x == y + + """ + msg = 'floats {!r} and {!r} are not identical' + + if math.isnan(x) or math.isnan(y): + if math.isnan(x) and math.isnan(y): + return + elif x == y: + if x != 0.0: + return + # both zero; check that signs match + elif math.copysign(1.0, x) == math.copysign(1.0, y): + return + else: + msg += ': zeros have different signs' + self.fail(msg.format(x, y)) + + def assertComplexesAreIdentical(self, x, y): + self.assertFloatsAreIdentical(x.real, y.real) + self.assertFloatsAreIdentical(x.imag, y.imag) + def test_isbigendian(self): self.assertEqual((struct.pack('=i', 1)[0] == 0), ISBIGENDIAN) @@ -774,6 +812,17 @@ def test_repr(self): s = struct.Struct('=i2H') self.assertEqual(repr(s), f'Struct({s.format!r})') + @unittest.skipUnless(have_c_complex, "requires C11 complex type") + def test_c_complex_round_trip(self): + values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2, + -3, INF, -INF, NAN], 2)] + for z in values: + for f in ['E', 'C', '>E', '>C', ' // offsetof() /*[clinic input] @@ -80,6 +83,10 @@ typedef struct { char c; int x; } st_int; typedef struct { char c; long x; } st_long; typedef struct { char c; float x; } st_float; typedef struct { char c; double x; } st_double; +#ifdef Py_HAVE_C_COMPLEX +typedef struct { char c; float complex x; } st_float_complex; +typedef struct { char c; double complex x; } st_double_complex; +#endif typedef struct { char c; void *x; } st_void_p; typedef struct { char c; size_t x; } st_size_t; typedef struct { char c; _Bool x; } st_bool; @@ -89,6 +96,10 @@ typedef struct { char c; _Bool x; } st_bool; #define LONG_ALIGN (sizeof(st_long) - sizeof(long)) #define FLOAT_ALIGN (sizeof(st_float) - sizeof(float)) #define DOUBLE_ALIGN (sizeof(st_double) - sizeof(double)) +#ifdef Py_HAVE_C_COMPLEX +#define FLOAT_COMPLEX_ALIGN (sizeof(st_float_complex) - sizeof(float complex)) +#define DOUBLE_COMPLEX_ALIGN (sizeof(st_double_complex) - sizeof(double complex)) +#endif #define VOID_P_ALIGN (sizeof(st_void_p) - sizeof(void *)) #define SIZE_T_ALIGN (sizeof(st_size_t) - sizeof(size_t)) #define BOOL_ALIGN (sizeof(st_bool) - sizeof(_Bool)) @@ -518,6 +529,26 @@ nu_double(_structmodulestate *state, const char *p, const formatdef *f) return PyFloat_FromDouble(x); } +#ifdef Py_HAVE_C_COMPLEX +static PyObject * +nu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) +{ + float complex x; + + memcpy(&x, p, sizeof(x)); + return PyComplex_FromDoubles(creal(x), cimag(x)); +} + +static PyObject * +nu_double_complex(_structmodulestate *state, const char *p, const formatdef *f) +{ + double complex x; + + memcpy(&x, p, sizeof(x)); + return PyComplex_FromDoubles(creal(x), cimag(x)); +} +#endif + static PyObject * nu_void_p(_structmodulestate *state, const char *p, const formatdef *f) { @@ -791,6 +822,40 @@ np_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) return 0; } +#ifdef Py_HAVE_C_COMPLEX +static int +np_float_complex(_structmodulestate *state, char *p, PyObject *v, + const formatdef *f) +{ + Py_complex c = PyComplex_AsCComplex(v); + float complex x = CMPLXF((float)c.real, (float)c.imag); + + if (c.real == -1 && PyErr_Occurred()) { + PyErr_SetString(state->StructError, + "required argument is not a complex"); + return -1; + } + memcpy(p, (char *)&x, sizeof(x)); + return 0; +} + +static int +np_double_complex(_structmodulestate *state, char *p, PyObject *v, + const formatdef *f) +{ + Py_complex c = PyComplex_AsCComplex(v); + double complex x = CMPLX(c.real, c.imag); + + if (c.real == -1 && PyErr_Occurred()) { + PyErr_SetString(state->StructError, + "required argument is not a complex"); + return -1; + } + memcpy(p, (char *)&x, sizeof(x)); + return 0; +} +#endif + static int np_void_p(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { @@ -829,6 +894,10 @@ static const formatdef native_table[] = { {'e', sizeof(short), SHORT_ALIGN, nu_halffloat, np_halffloat}, {'f', sizeof(float), FLOAT_ALIGN, nu_float, np_float}, {'d', sizeof(double), DOUBLE_ALIGN, nu_double, np_double}, +#ifdef Py_HAVE_C_COMPLEX + {'E', sizeof(float complex), FLOAT_COMPLEX_ALIGN, nu_float_complex, np_float_complex}, + {'C', sizeof(double complex), DOUBLE_COMPLEX_ALIGN, nu_double_complex, np_double_complex}, +#endif {'P', sizeof(void *), VOID_P_ALIGN, nu_void_p, np_void_p}, {0} }; @@ -929,6 +998,40 @@ bu_double(_structmodulestate *state, const char *p, const formatdef *f) return unpack_double(p, 0); } +#ifdef Py_HAVE_C_COMPLEX +static PyObject * +bu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) +{ + double x, y; + + x = PyFloat_Unpack4(p, 0); + if (x == -1.0 && PyErr_Occurred()) { + return NULL; + } + y = PyFloat_Unpack4(p + 4, 0); + if (y == -1.0 && PyErr_Occurred()) { + return NULL; + } + return PyComplex_FromDoubles(x, y); +} + +static PyObject * +bu_double_complex(_structmodulestate *state, const char *p, const formatdef *f) +{ + double x, y; + + x = PyFloat_Unpack8(p, 0); + if (x == -1.0 && PyErr_Occurred()) { + return NULL; + } + y = PyFloat_Unpack8(p + 8, 0); + if (y == -1.0 && PyErr_Occurred()) { + return NULL; + } + return PyComplex_FromDoubles(x, y); +} +#endif + static PyObject * bu_bool(_structmodulestate *state, const char *p, const formatdef *f) { @@ -1068,6 +1171,38 @@ bp_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) return PyFloat_Pack8(x, p, 0); } +#ifdef Py_HAVE_C_COMPLEX +static int +bp_float_complex(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) +{ + Py_complex x = PyComplex_AsCComplex(v); + if (x.real == -1 && PyErr_Occurred()) { + PyErr_SetString(state->StructError, + "required argument is not a complex"); + return -1; + } + if (PyFloat_Pack4(x.real, p, 0)) { + return -1; + } + return PyFloat_Pack4(x.imag, p + 4, 0); +} + +static int +bp_double_complex(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) +{ + Py_complex x = PyComplex_AsCComplex(v); + if (x.real == -1 && PyErr_Occurred()) { + PyErr_SetString(state->StructError, + "required argument is not a complex"); + return -1; + } + if (PyFloat_Pack8(x.real, p, 0)) { + return -1; + } + return PyFloat_Pack8(x.imag, p + 8, 0); +} +#endif + static int bp_bool(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { @@ -1098,6 +1233,10 @@ static formatdef bigendian_table[] = { {'e', 2, 0, bu_halffloat, bp_halffloat}, {'f', 4, 0, bu_float, bp_float}, {'d', 8, 0, bu_double, bp_double}, +#ifdef Py_HAVE_C_COMPLEX + {'E', 8, 0, bu_float_complex, bp_float_complex}, + {'C', 16, 0, bu_double_complex, bp_double_complex}, +#endif {0} }; @@ -1197,6 +1336,40 @@ lu_double(_structmodulestate *state, const char *p, const formatdef *f) return unpack_double(p, 1); } +#ifdef Py_HAVE_C_COMPLEX +static PyObject * +lu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) +{ + double x, y; + + x = PyFloat_Unpack4(p, 1); + if (x == -1.0 && PyErr_Occurred()) { + return NULL; + } + y = PyFloat_Unpack4(p + 4, 1); + if (y == -1.0 && PyErr_Occurred()) { + return NULL; + } + return PyComplex_FromDoubles(x, y); +} + +static PyObject * +lu_double_complex(_structmodulestate *state, const char *p, const formatdef *f) +{ + double x, y; + + x = PyFloat_Unpack8(p, 1); + if (x == -1.0 && PyErr_Occurred()) { + return NULL; + } + y = PyFloat_Unpack8(p + 8, 1); + if (y == -1.0 && PyErr_Occurred()) { + return NULL; + } + return PyComplex_FromDoubles(x, y); +} +#endif + static int lp_int(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) { @@ -1330,6 +1503,39 @@ lp_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) return PyFloat_Pack8(x, p, 1); } +#ifdef Py_HAVE_C_COMPLEX +static int +lp_float_complex(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) +{ + Py_complex x = PyComplex_AsCComplex(v); + if (x.real == -1 && PyErr_Occurred()) { + PyErr_SetString(state->StructError, + "required argument is not a complex"); + return -1; + } + if (PyFloat_Pack4(x.real, p, 1)) { + return -1; + } + return PyFloat_Pack4(x.imag, p + 4, 1); + +} + +static int +lp_double_complex(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) +{ + Py_complex x = PyComplex_AsCComplex(v); + if (x.real == -1 && PyErr_Occurred()) { + PyErr_SetString(state->StructError, + "required argument is not a complex"); + return -1; + } + if (PyFloat_Pack8(x.real, p, 1)) { + return -1; + } + return PyFloat_Pack8(x.imag, p + 4, 1); +} +#endif + static formatdef lilendian_table[] = { {'x', 1, 0, NULL}, {'b', 1, 0, nu_byte, np_byte}, @@ -1350,6 +1556,10 @@ static formatdef lilendian_table[] = { {'e', 2, 0, lu_halffloat, lp_halffloat}, {'f', 4, 0, lu_float, lp_float}, {'d', 8, 0, lu_double, lp_double}, +#ifdef Py_HAVE_C_COMPLEX + {'E', 8, 0, lu_float_complex, lp_float_complex}, + {'C', 16, 0, lu_double_complex, lp_double_complex}, +#endif {0} }; From 3aac6d1e0ccd88047377786bc2b74cebbe9daf64 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 11 Jul 2024 19:01:31 +0300 Subject: [PATCH 02/10] Apply suggestions from code review Co-authored-by: Peter Bierma --- .../Library/2024-07-10-08-13-34.gh-issue-121249.W9Gd09.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2024-07-10-08-13-34.gh-issue-121249.W9Gd09.rst b/Misc/NEWS.d/next/Library/2024-07-10-08-13-34.gh-issue-121249.W9Gd09.rst index b795cdad1a424e..2d41fca45bcad9 100644 --- a/Misc/NEWS.d/next/Library/2024-07-10-08-13-34.gh-issue-121249.W9Gd09.rst +++ b/Misc/NEWS.d/next/Library/2024-07-10-08-13-34.gh-issue-121249.W9Gd09.rst @@ -1,3 +1,3 @@ -Support :c:expr:`float complex` and :c:expr:`double complex` -C types in the :mod:`struct` if compiler has C11 complex +Support the :c:expr:`float complex` and :c:expr:`double complex` +C types in the :mod:`struct` module if the compiler has C11 complex arithmetic. Patch by Sergey B Kirpichev. From 785396aa87c18d8ca684a6e4bd8c66250eebb029 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 13 Jul 2024 05:31:57 +0300 Subject: [PATCH 03/10] Update Doc/library/struct.rst --- Doc/library/struct.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/struct.rst b/Doc/library/struct.rst index e0fae2b85a90f8..13e3244f4c70dc 100644 --- a/Doc/library/struct.rst +++ b/Doc/library/struct.rst @@ -267,8 +267,8 @@ platform-dependent. | ``P`` | :c:expr:`void \*` | integer | | \(5) | +--------+--------------------------+--------------------+----------------+------------+ -Additionally, if IEC 60559 compatible complex arithmetic (Annex G) is -supported, the following format characters are available: +Additionally, if IEC 60559 compatible complex arithmetic (Annex G of the +C11 standard) is supported, the following format characters are available: +--------+--------------------------+--------------------+----------------+------------+ | Format | C Type | Python type | Standard size | Notes | From e5e04c88bc1bc57a1ea62abccd4057375f5e7909 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 13 Jul 2024 05:34:04 +0300 Subject: [PATCH 04/10] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Doc/library/struct.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/struct.rst b/Doc/library/struct.rst index 13e3244f4c70dc..8078cb5f0fe26a 100644 --- a/Doc/library/struct.rst +++ b/Doc/library/struct.rst @@ -285,7 +285,7 @@ C11 standard) is supported, the following format characters are available: Added support for the ``'e'`` format. .. versionchanged:: 3.14 - Added support for ``'E'`` and ``'C'`` formats. + Added support for the ``'E'`` and ``'C'`` formats. Notes: @@ -364,7 +364,7 @@ Notes: ``'0c'`` means 0 characters). (10) - For the ``'E'`` and ``'C'`` conversion codes, the packed representation uses + For the ``'E'`` and ``'C'`` format characters, the packed representation uses the IEEE 754 binary32 and binary64 format for components of the complex number, regardless of the floating-point format used by the platform. From fec6be60230249a12476060843cb644aba347a7c Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 13 Jul 2024 06:48:20 +0300 Subject: [PATCH 05/10] raise a custom error on systems without native complex numbers --- Lib/test/test_struct.py | 14 +++++++++++++- Modules/_struct.c | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 50bd020e32bc56..fabc334c9defb6 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -812,8 +812,20 @@ def test_repr(self): s = struct.Struct('=i2H') self.assertEqual(repr(s), f'Struct({s.format!r})') - @unittest.skipUnless(have_c_complex, "requires C11 complex type") def test_c_complex_round_trip(self): + if not have_c_complex: + msg1 = "'E' format not supported on this system" + msg2 = "'C' format not supported on this system" + with self.assertRaisesRegex(struct.error, msg1): + struct.pack('E', 1j) + with self.assertRaisesRegex(struct.error, msg1): + struct.unpack('E', b'1') + with self.assertRaisesRegex(struct.error, msg2): + struct.pack('C', 1j) + with self.assertRaisesRegex(struct.error, msg2): + struct.unpack('C', b'1') + return + values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2, -3, INF, -INF, NAN], 2)] for z in values: diff --git a/Modules/_struct.c b/Modules/_struct.c index 73eaade98c838c..32fb8bd07b3ccb 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -854,6 +854,24 @@ np_double_complex(_structmodulestate *state, char *p, PyObject *v, memcpy(p, (char *)&x, sizeof(x)); return 0; } +#else +static int +np_complex_stub(_structmodulestate *state, char *p, PyObject *v, + const formatdef *f) +{ + PyErr_Format(state->StructError, + "'%c' format not supported on this system", + f->format); + return -1; +} +static PyObject * +nu_complex_stub(_structmodulestate *state, const char *p, const formatdef *f) +{ + PyErr_Format(state->StructError, + "'%c' format not supported on this system", + f->format); + return NULL; +} #endif static int @@ -897,6 +915,9 @@ static const formatdef native_table[] = { #ifdef Py_HAVE_C_COMPLEX {'E', sizeof(float complex), FLOAT_COMPLEX_ALIGN, nu_float_complex, np_float_complex}, {'C', sizeof(double complex), DOUBLE_COMPLEX_ALIGN, nu_double_complex, np_double_complex}, +#else + {'E', 1, 0, nu_complex_stub, np_complex_stub}, + {'C', 1, 0, nu_complex_stub, np_complex_stub}, #endif {'P', sizeof(void *), VOID_P_ALIGN, nu_void_p, np_void_p}, {0} @@ -1236,6 +1257,9 @@ static formatdef bigendian_table[] = { #ifdef Py_HAVE_C_COMPLEX {'E', 8, 0, bu_float_complex, bp_float_complex}, {'C', 16, 0, bu_double_complex, bp_double_complex}, +#else + {'E', 1, 0, nu_complex_stub, np_complex_stub}, + {'C', 1, 0, nu_complex_stub, np_complex_stub}, #endif {0} }; @@ -1559,6 +1583,9 @@ static formatdef lilendian_table[] = { #ifdef Py_HAVE_C_COMPLEX {'E', 8, 0, lu_float_complex, lp_float_complex}, {'C', 16, 0, lu_double_complex, lp_double_complex}, +#else + {'E', 1, 0, nu_complex_stub, np_complex_stub}, + {'C', 1, 0, nu_complex_stub, np_complex_stub}, #endif {0} }; From caaef44f08b2c05a6a59932f52ff19213fa2409f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 13 Jul 2024 10:56:04 +0300 Subject: [PATCH 06/10] address review: separate tests --- Lib/test/test_struct.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index fabc334c9defb6..514e887dad3e10 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -812,20 +812,8 @@ def test_repr(self): s = struct.Struct('=i2H') self.assertEqual(repr(s), f'Struct({s.format!r})') + @unittest.skipUnless(have_c_complex, "requires C11 complex type") def test_c_complex_round_trip(self): - if not have_c_complex: - msg1 = "'E' format not supported on this system" - msg2 = "'C' format not supported on this system" - with self.assertRaisesRegex(struct.error, msg1): - struct.pack('E', 1j) - with self.assertRaisesRegex(struct.error, msg1): - struct.unpack('E', b'1') - with self.assertRaisesRegex(struct.error, msg2): - struct.pack('C', 1j) - with self.assertRaisesRegex(struct.error, msg2): - struct.unpack('C', b'1') - return - values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2, -3, INF, -INF, NAN], 2)] for z in values: @@ -834,6 +822,19 @@ def test_c_complex_round_trip(self): round_trip = struct.unpack(f, struct.pack(f, z))[0] self.assertComplexesAreIdentical(z, round_trip) + @unittest.skipIf(have_c_complex, "requires no C11 complex type") + def test_c_complex_error(self): + msg1 = "'E' format not supported on this system" + msg2 = "'C' format not supported on this system" + with self.assertRaisesRegex(struct.error, msg1): + struct.pack('E', 1j) + with self.assertRaisesRegex(struct.error, msg1): + struct.unpack('E', b'1') + with self.assertRaisesRegex(struct.error, msg2): + struct.pack('C', 1j) + with self.assertRaisesRegex(struct.error, msg2): + struct.unpack('C', b'1') + class UnpackIteratorTest(unittest.TestCase): """ From 4097aaf547aef6d9a3fd8d1a9dc297e9503f4571 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 7 Oct 2024 07:05:11 +0300 Subject: [PATCH 07/10] + use ComplexesAreIdenticalMixin from test.support --- Lib/test/test_struct.py | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 2ca9d4de72d2a7..68c82a643e7db2 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -12,6 +12,7 @@ from test import support from test.support import import_helper, suppress_immortalization from test.support.script_helper import assert_python_ok +from test.support.testcase import ComplexesAreIdenticalMixin ISBIGENDIAN = sys.byteorder == "big" @@ -43,35 +44,7 @@ def bigendian_to_native(value): else: return string_reverse(value) -class StructTest(unittest.TestCase): - # from Lib/test/test_complex.py - def assertFloatsAreIdentical(self, x, y): - """assert that floats x and y are identical, in the sense that: - (1) both x and y are nans, or - (2) both x and y are infinities, with the same sign, or - (3) both x and y are zeros, with the same sign, or - (4) x and y are both finite and nonzero, and x == y - - """ - msg = 'floats {!r} and {!r} are not identical' - - if math.isnan(x) or math.isnan(y): - if math.isnan(x) and math.isnan(y): - return - elif x == y: - if x != 0.0: - return - # both zero; check that signs match - elif math.copysign(1.0, x) == math.copysign(1.0, y): - return - else: - msg += ': zeros have different signs' - self.fail(msg.format(x, y)) - - def assertComplexesAreIdentical(self, x, y): - self.assertFloatsAreIdentical(x.real, y.real) - self.assertFloatsAreIdentical(x.imag, y.imag) - +class StructTest(ComplexesAreIdenticalMixin, unittest.TestCase): def test_isbigendian(self): self.assertEqual((struct.pack('=i', 1)[0] == 0), ISBIGENDIAN) From 5fe6c3557b09af04224cd629021ba2104a70cf84 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 7 Oct 2024 12:17:08 +0300 Subject: [PATCH 08/10] Apply suggestions from code review Co-authored-by: Victor Stinner --- Lib/test/test_struct.py | 4 ++-- Modules/_struct.c | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_struct.py b/Lib/test/test_struct.py index 68c82a643e7db2..e3193c7863fbae 100644 --- a/Lib/test/test_struct.py +++ b/Lib/test/test_struct.py @@ -794,7 +794,7 @@ def test_repr(self): s = struct.Struct('=i2H') self.assertEqual(repr(s), f'Struct({s.format!r})') - @unittest.skipUnless(have_c_complex, "requires C11 complex type") + @unittest.skipUnless(have_c_complex, "requires C11 complex type support") def test_c_complex_round_trip(self): values = [complex(*_) for _ in combinations([1, -1, 0.0, -0.0, 2, -3, INF, -INF, NAN], 2)] @@ -804,7 +804,7 @@ def test_c_complex_round_trip(self): round_trip = struct.unpack(f, struct.pack(f, z))[0] self.assertComplexesAreIdentical(z, round_trip) - @unittest.skipIf(have_c_complex, "requires no C11 complex type") + @unittest.skipIf(have_c_complex, "requires no C11 complex type support") def test_c_complex_error(self): msg1 = "'E' format not supported on this system" msg2 = "'C' format not supported on this system" diff --git a/Modules/_struct.c b/Modules/_struct.c index de25309c204054..dd75b47831f477 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -97,8 +97,8 @@ typedef struct { char c; _Bool x; } st_bool; #define FLOAT_ALIGN (sizeof(st_float) - sizeof(float)) #define DOUBLE_ALIGN (sizeof(st_double) - sizeof(double)) #ifdef Py_HAVE_C_COMPLEX -#define FLOAT_COMPLEX_ALIGN (sizeof(st_float_complex) - sizeof(float complex)) -#define DOUBLE_COMPLEX_ALIGN (sizeof(st_double_complex) - sizeof(double complex)) +# define FLOAT_COMPLEX_ALIGN (sizeof(st_float_complex) - sizeof(float complex)) +# define DOUBLE_COMPLEX_ALIGN (sizeof(st_double_complex) - sizeof(double complex)) #endif #define VOID_P_ALIGN (sizeof(st_void_p) - sizeof(void *)) #define SIZE_T_ALIGN (sizeof(st_size_t) - sizeof(size_t)) @@ -1023,13 +1023,11 @@ bu_double(_structmodulestate *state, const char *p, const formatdef *f) static PyObject * bu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) { - double x, y; - - x = PyFloat_Unpack4(p, 0); + double x = PyFloat_Unpack4(p, 0); if (x == -1.0 && PyErr_Occurred()) { return NULL; } - y = PyFloat_Unpack4(p + 4, 0); + double y = PyFloat_Unpack4(p + 4, 0); if (y == -1.0 && PyErr_Occurred()) { return NULL; } @@ -1364,13 +1362,11 @@ lu_double(_structmodulestate *state, const char *p, const formatdef *f) static PyObject * lu_float_complex(_structmodulestate *state, const char *p, const formatdef *f) { - double x, y; - - x = PyFloat_Unpack4(p, 1); + double x = PyFloat_Unpack4(p, 1); if (x == -1.0 && PyErr_Occurred()) { return NULL; } - y = PyFloat_Unpack4(p + 4, 1); + double y = PyFloat_Unpack4(p + 4, 1); if (y == -1.0 && PyErr_Occurred()) { return NULL; } From 4820f1982e21ad10c52c4e36f365bb6a9397528d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 7 Oct 2024 12:47:34 +0300 Subject: [PATCH 09/10] + fixed typo in lp_double_complex() --- Modules/_struct.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index dd75b47831f477..21e3d89df4bf8c 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -1552,7 +1552,7 @@ lp_double_complex(_structmodulestate *state, char *p, PyObject *v, const formatd if (PyFloat_Pack8(x.real, p, 1)) { return -1; } - return PyFloat_Pack8(x.imag, p + 4, 1); + return PyFloat_Pack8(x.imag, p + 8, 1); } #endif From fbd189f89dddd770b64597358a75744524bf34a3 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 7 Oct 2024 13:12:58 +0300 Subject: [PATCH 10/10] address review: clean up useless type casts in the struct module --- Modules/_struct.c | 60 +++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Modules/_struct.c b/Modules/_struct.c index 21e3d89df4bf8c..4387c55b7c8848 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -418,7 +418,7 @@ static PyObject * nu_short(_structmodulestate *state, const char *p, const formatdef *f) { short x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromLong((long)x); } @@ -426,7 +426,7 @@ static PyObject * nu_ushort(_structmodulestate *state, const char *p, const formatdef *f) { unsigned short x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromLong((long)x); } @@ -434,7 +434,7 @@ static PyObject * nu_int(_structmodulestate *state, const char *p, const formatdef *f) { int x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromLong((long)x); } @@ -442,7 +442,7 @@ static PyObject * nu_uint(_structmodulestate *state, const char *p, const formatdef *f) { unsigned int x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromUnsignedLong((unsigned long)x); } @@ -450,7 +450,7 @@ static PyObject * nu_long(_structmodulestate *state, const char *p, const formatdef *f) { long x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromLong(x); } @@ -458,7 +458,7 @@ static PyObject * nu_ulong(_structmodulestate *state, const char *p, const formatdef *f) { unsigned long x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromUnsignedLong(x); } @@ -466,7 +466,7 @@ static PyObject * nu_ssize_t(_structmodulestate *state, const char *p, const formatdef *f) { Py_ssize_t x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromSsize_t(x); } @@ -474,7 +474,7 @@ static PyObject * nu_size_t(_structmodulestate *state, const char *p, const formatdef *f) { size_t x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromSize_t(x); } @@ -482,7 +482,7 @@ static PyObject * nu_longlong(_structmodulestate *state, const char *p, const formatdef *f) { long long x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromLongLong(x); } @@ -490,7 +490,7 @@ static PyObject * nu_ulonglong(_structmodulestate *state, const char *p, const formatdef *f) { unsigned long long x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromUnsignedLongLong(x); } @@ -498,7 +498,7 @@ static PyObject * nu_bool(_structmodulestate *state, const char *p, const formatdef *f) { _Bool x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyBool_FromLong(x != 0); } @@ -517,7 +517,7 @@ static PyObject * nu_float(_structmodulestate *state, const char *p, const formatdef *f) { float x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyFloat_FromDouble((double)x); } @@ -525,7 +525,7 @@ static PyObject * nu_double(_structmodulestate *state, const char *p, const formatdef *f) { double x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyFloat_FromDouble(x); } @@ -553,7 +553,7 @@ static PyObject * nu_void_p(_structmodulestate *state, const char *p, const formatdef *f) { void *x; - memcpy((char *)&x, p, sizeof x); + memcpy(&x, p, sizeof x); return PyLong_FromVoidPtr(x); } @@ -618,7 +618,7 @@ np_short(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) RANGE_ERROR(state, f, 0); } y = (short)x; - memcpy(p, (char *)&y, sizeof y); + memcpy(p, &y, sizeof y); return 0; } @@ -637,7 +637,7 @@ np_ushort(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) RANGE_ERROR(state, f, 1); } y = (unsigned short)x; - memcpy(p, (char *)&y, sizeof y); + memcpy(p, &y, sizeof y); return 0; } @@ -657,7 +657,7 @@ np_int(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) RANGE_ERROR(state, f, 0); #endif y = (int)x; - memcpy(p, (char *)&y, sizeof y); + memcpy(p, &y, sizeof y); return 0; } @@ -677,7 +677,7 @@ np_uint(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) if (x > ((unsigned long)UINT_MAX)) RANGE_ERROR(state, f, 1); #endif - memcpy(p, (char *)&y, sizeof y); + memcpy(p, &y, sizeof y); return 0; } @@ -691,7 +691,7 @@ np_long(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) } return -1; } - memcpy(p, (char *)&x, sizeof x); + memcpy(p, &x, sizeof x); return 0; } @@ -705,7 +705,7 @@ np_ulong(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) } return -1; } - memcpy(p, (char *)&x, sizeof x); + memcpy(p, &x, sizeof x); return 0; } @@ -719,7 +719,7 @@ np_ssize_t(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) } return -1; } - memcpy(p, (char *)&x, sizeof x); + memcpy(p, &x, sizeof x); return 0; } @@ -733,7 +733,7 @@ np_size_t(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) } return -1; } - memcpy(p, (char *)&x, sizeof x); + memcpy(p, &x, sizeof x); return 0; } @@ -751,7 +751,7 @@ np_longlong(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) } return -1; } - memcpy(p, (char *)&x, sizeof x); + memcpy(p, &x, sizeof x); return 0; } @@ -768,7 +768,7 @@ np_ulonglong(_structmodulestate *state, char *p, PyObject *v, const formatdef *f } return -1; } - memcpy(p, (char *)&x, sizeof x); + memcpy(p, &x, sizeof x); return 0; } @@ -782,7 +782,7 @@ np_bool(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) if (y < 0) return -1; x = y; - memcpy(p, (char *)&x, sizeof x); + memcpy(p, &x, sizeof x); return 0; } @@ -805,7 +805,7 @@ np_float(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) "required argument is not a float"); return -1; } - memcpy(p, (char *)&x, sizeof x); + memcpy(p, &x, sizeof x); return 0; } @@ -818,7 +818,7 @@ np_double(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) "required argument is not a float"); return -1; } - memcpy(p, (char *)&x, sizeof(double)); + memcpy(p, &x, sizeof(double)); return 0; } @@ -835,7 +835,7 @@ np_float_complex(_structmodulestate *state, char *p, PyObject *v, "required argument is not a complex"); return -1; } - memcpy(p, (char *)&x, sizeof(x)); + memcpy(p, &x, sizeof(x)); return 0; } @@ -851,7 +851,7 @@ np_double_complex(_structmodulestate *state, char *p, PyObject *v, "required argument is not a complex"); return -1; } - memcpy(p, (char *)&x, sizeof(x)); + memcpy(p, &x, sizeof(x)); return 0; } #else @@ -887,7 +887,7 @@ np_void_p(_structmodulestate *state, char *p, PyObject *v, const formatdef *f) Py_DECREF(v); if (x == NULL && PyErr_Occurred()) return -1; - memcpy(p, (char *)&x, sizeof x); + memcpy(p, &x, sizeof x); return 0; }