Skip to content

Commit 5cdaaf8

Browse files
committed
Add nb_complex slot to PyNumberMethods (was nb_reserved)
1 parent 9b0ab3a commit 5cdaaf8

File tree

21 files changed

+147
-104
lines changed

21 files changed

+147
-104
lines changed

Doc/c-api/typeobj.rst

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ sub-slots
276276
+---------------------------------------------------------+-----------------------------------+---------------+
277277
| :c:member:`~PyNumberMethods.nb_int` | :c:type:`unaryfunc` | __int__ |
278278
+---------------------------------------------------------+-----------------------------------+---------------+
279-
| :c:member:`~PyNumberMethods.nb_reserved` | void * | |
279+
| :c:member:`~PyNumberMethods.nb_complex` | :c:type:`unaryfunc` | __complex__ |
280280
+---------------------------------------------------------+-----------------------------------+---------------+
281281
| :c:member:`~PyNumberMethods.nb_float` | :c:type:`unaryfunc` | __float__ |
282282
+---------------------------------------------------------+-----------------------------------+---------------+
@@ -2304,7 +2304,7 @@ Number Object Structures
23042304
binaryfunc nb_xor;
23052305
binaryfunc nb_or;
23062306
unaryfunc nb_int;
2307-
void *nb_reserved;
2307+
unaryfunc nb_complex;
23082308
unaryfunc nb_float;
23092309

23102310
binaryfunc nb_inplace_add;
@@ -2338,11 +2338,6 @@ Number Object Structures
23382338
``Py_NotImplemented``, if another error occurred they must return ``NULL``
23392339
and set an exception.
23402340

2341-
.. note::
2342-
2343-
The :c:member:`~PyNumberMethods.nb_reserved` field should always be ``NULL``. It
2344-
was previously called :c:member:`!nb_long`, and was renamed in
2345-
Python 3.0.1.
23462341

23472342
.. c:member:: binaryfunc PyNumberMethods.nb_add
23482343
.. c:member:: binaryfunc PyNumberMethods.nb_subtract
@@ -2361,7 +2356,7 @@ Number Object Structures
23612356
.. c:member:: binaryfunc PyNumberMethods.nb_xor
23622357
.. c:member:: binaryfunc PyNumberMethods.nb_or
23632358
.. c:member:: unaryfunc PyNumberMethods.nb_int
2364-
.. c:member:: void *PyNumberMethods.nb_reserved
2359+
.. c:member:: unaryfunc PyNumberMethods.nb_complex
23652360
.. c:member:: unaryfunc PyNumberMethods.nb_float
23662361
.. c:member:: binaryfunc PyNumberMethods.nb_inplace_add
23672362
.. c:member:: binaryfunc PyNumberMethods.nb_inplace_subtract

Include/cpython/object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ typedef struct {
8080
binaryfunc nb_xor;
8181
binaryfunc nb_or;
8282
unaryfunc nb_int;
83-
void *nb_reserved; /* the slot formerly known as nb_long */
83+
unaryfunc nb_complex; /* the slot formerly known as nb_long */
8484
unaryfunc nb_float;
8585

8686
binaryfunc nb_inplace_add;

Include/typeslots.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,7 @@
9494
/* New in 3.14 */
9595
#define Py_tp_token 83
9696
#endif
97+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000
98+
/* New in 3.14 */
99+
#define Py_nb_complex 84
100+
#endif

Lib/test/test_capi/test_complex.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,16 @@ def test_py_c_abs(self):
306306

307307
self.assertEqual(_py_c_abs(complex(*[DBL_MAX]*2))[1], errno.ERANGE)
308308

309+
def test_old__complex__(self):
310+
old_complex_like = _testcapi.old_complex_like
311+
312+
x = old_complex_like()
313+
with self.assertWarns(DeprecationWarning):
314+
self.assertEqual(complex(x), 1+2j)
315+
with warnings.catch_warnings():
316+
warnings.simplefilter("error", DeprecationWarning)
317+
self.assertRaises(DeprecationWarning, complex, x)
318+
309319

310320
if __name__ == "__main__":
311321
unittest.main()

Lib/test/test_capi/test_number.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ def test_check(self):
8383
self.assertTrue(check(0.5))
8484
self.assertTrue(check(FloatLike.with_val(4.25)))
8585
self.assertTrue(check(1+2j))
86+
self.assertTrue(check(ComplexLike.with_val(1+2j)))
8687

8788
self.assertFalse(check([]))
8889
self.assertFalse(check("abc"))

Misc/stable_abi.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@
238238
added = '3.2'
239239
[const.Py_nb_float]
240240
added = '3.2'
241+
[const.Py_nb_complex]
242+
added = '3.14'
241243
[const.Py_nb_inplace_add]
242244
added = '3.2'
243245
[const.Py_nb_inplace_subtract]

Modules/_datetimemodule.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3057,7 +3057,7 @@ static PyNumberMethods delta_as_number = {
30573057
0, /*nb_xor*/
30583058
0, /*nb_or*/
30593059
0, /*nb_int*/
3060-
0, /*nb_reserved*/
3060+
0, /*nb_complex*/
30613061
0, /*nb_float*/
30623062
0, /*nb_inplace_add*/
30633063
0, /*nb_inplace_subtract*/

Modules/_decimal/_decimal.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4841,7 +4841,7 @@ dec_ceil(PyObject *self, PyObject *Py_UNUSED(dummy))
48414841

48424842
/* __complex__ */
48434843
static PyObject *
4844-
dec_complex(PyObject *self, PyObject *Py_UNUSED(dummy))
4844+
dec_complex(PyObject *self)
48454845
{
48464846
PyObject *f;
48474847
double x;
@@ -5161,7 +5161,6 @@ static PyMethodDef dec_methods [] =
51615161
{ "__ceil__", dec_ceil, METH_NOARGS, NULL },
51625162
{ "__floor__", dec_floor, METH_NOARGS, NULL },
51635163
{ "__trunc__", dec_trunc, METH_NOARGS, NULL },
5164-
{ "__complex__", dec_complex, METH_NOARGS, NULL },
51655164
{ "__sizeof__", dec_sizeof, METH_NOARGS, NULL },
51665165

51675166
{ NULL, NULL, 1 }
@@ -5194,6 +5193,7 @@ static PyType_Slot dec_slots[] = {
51945193
{Py_nb_bool, nm_nonzero},
51955194
{Py_nb_int, nm_dec_as_long},
51965195
{Py_nb_float, PyDec_AsFloat},
5196+
{Py_nb_complex, dec_complex},
51975197
{Py_nb_floor_divide, nm_mpd_qdivint},
51985198
{Py_nb_true_divide, nm_mpd_qdiv},
51995199
{0, NULL},

Modules/_testcapimodule.c

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2684,7 +2684,7 @@ static PyNumberMethods matmulType_as_number = {
26842684
0, /* nb_xor */
26852685
0, /* nb_or */
26862686
0, /* nb_int */
2687-
0, /* nb_reserved */
2687+
0, /* nb_complex */
26882688
0, /* nb_float */
26892689
0, /* nb_inplace_add */
26902690
0, /* nb_inplace_subtract */
@@ -3175,6 +3175,28 @@ create_manual_heap_type(void)
31753175
return (PyObject *)type;
31763176
}
31773177

3178+
/* Complex-like type with a __complex__ method, instead of nb_complex slot,
3179+
to test deprecation. */
3180+
3181+
static PyObject *
3182+
complex_dunder(PyObject *self, PyObject *Py_UNUSED(dummy))
3183+
{
3184+
return PyComplex_FromDoubles(1, 2);
3185+
}
3186+
3187+
static PyMethodDef old_complex_methods[] = {
3188+
{"__complex__", complex_dunder, METH_NOARGS},
3189+
{NULL, NULL} /* sentinel */
3190+
};
3191+
3192+
PyTypeObject OldComplexLikeType = {
3193+
PyVarObject_HEAD_INIT(NULL, 0)
3194+
.tp_name = "old_complex_like",
3195+
.tp_new = PyType_GenericNew,
3196+
.tp_methods = old_complex_methods,
3197+
};
3198+
3199+
31783200
static struct PyModuleDef _testcapimodule = {
31793201
PyModuleDef_HEAD_INIT,
31803202
.m_name = "_testcapi",
@@ -3315,6 +3337,10 @@ PyInit__testcapi(void)
33153337
return NULL;
33163338
}
33173339

3340+
if (PyType_Ready(&OldComplexLikeType) < 0)
3341+
return NULL;
3342+
Py_INCREF(&OldComplexLikeType);
3343+
PyModule_AddObject(m, "old_complex_like", (PyObject *)&OldComplexLikeType);
33183344

33193345
/* Include tests from the _testcapi/ directory */
33203346
if (_PyTestCapi_Init_Vectorcall(m) < 0) {

Objects/abstract.c

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ PyNumber_Check(PyObject *o)
905905
if (o == NULL)
906906
return 0;
907907
PyNumberMethods *nb = Py_TYPE(o)->tp_as_number;
908-
return nb && (nb->nb_index || nb->nb_int || nb->nb_float || PyComplex_Check(o));
908+
return nb && (nb->nb_index || nb->nb_int || nb->nb_float || nb->nb_complex);
909909
}
910910

911911
/* Binary operators */
@@ -1659,44 +1659,61 @@ PyNumber_Complex(PyObject *o)
16591659
return Py_NewRef(o);
16601660
}
16611661

1662-
PyObject *f = _PyObject_LookupSpecial(o, &_Py_ID(__complex__));
1663-
if (f) {
1664-
PyObject *res = _PyObject_CallNoArgs(f);
1665-
Py_DECREF(f);
1666-
if (!res || PyComplex_CheckExact(res)) {
1667-
return res;
1668-
}
1669-
if (!PyComplex_Check(res)) {
1670-
PyErr_Format(PyExc_TypeError,
1671-
"%.50s.__complex__ returned non-complex (type %.50s)",
1672-
Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name);
1673-
Py_DECREF(res);
1674-
return NULL;
1675-
}
1676-
/* Issue #26983: warn if 'res' not of exact type complex. */
1677-
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
1678-
"%.50s.__complex__ returned non-complex (type %.50s). "
1679-
"The ability to return an instance of a strict subclass of complex "
1680-
"is deprecated, and may be removed in a future version of Python.",
1681-
Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name)) {
1682-
Py_DECREF(res);
1683-
return NULL;
1662+
PyNumberMethods *m = Py_TYPE(o)->tp_as_number;
1663+
PyObject *res = NULL;
1664+
1665+
if (m && m->nb_complex) {
1666+
res = m->nb_complex(o);
1667+
goto complex_slot;
1668+
}
1669+
else {
1670+
PyObject *f = _PyObject_LookupSpecial(o, &_Py_ID(__complex__));
1671+
if (f) {
1672+
res = _PyObject_CallNoArgs(f);
1673+
Py_DECREF(f);
1674+
if (PyErr_WarnEx(PyExc_DeprecationWarning,
1675+
"Use nb_complex slot to implement __complex__", 1)) {
1676+
Py_XDECREF(res);
1677+
return NULL;
1678+
}
1679+
goto complex_slot;
16841680
}
1685-
Py_complex cv = ((PyComplexObject *)res)->cval;
1686-
Py_DECREF(res);
1687-
return PyComplex_FromCComplex(cv);
16881681
}
1689-
1690-
PyNumberMethods *m = Py_TYPE(o)->tp_as_number;
1682+
m = Py_TYPE(o)->tp_as_number;
16911683
if (m && (m->nb_float || m->nb_index)) {
16921684
double real = PyFloat_AsDouble(o);
16931685
if (real != -1 || !PyErr_Occurred()) {
16941686
return PyComplex_FromDoubles(real, 0.0);
16951687
}
16961688
return NULL;
16971689
}
1698-
16991690
return PyComplex_FromString(o);
1691+
complex_slot:
1692+
if (!res) {
1693+
return NULL;
1694+
}
1695+
if (PyComplex_CheckExact(res)) {
1696+
return res;
1697+
}
1698+
if (!PyComplex_Check(res)) {
1699+
PyErr_Format(PyExc_TypeError,
1700+
"%.50s.__complex__ returned non-complex (type %.50s)",
1701+
Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name);
1702+
Py_DECREF(res);
1703+
return NULL;
1704+
}
1705+
/* Issue #26983: warn if 'res' not of exact type complex. */
1706+
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
1707+
"%.50s.__complex__ returned non-complex (type %.50s). "
1708+
"The ability to return an instance of a strict subclass of complex "
1709+
"is deprecated, and may be removed in a future version of Python.",
1710+
Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name)) {
1711+
Py_DECREF(res);
1712+
return NULL;
1713+
}
1714+
Py_complex cv = ((PyComplexObject *)res)->cval;
1715+
Py_DECREF(res);
1716+
return PyComplex_FromCComplex(cv);
17001717
}
17011718

17021719
PyObject *

Objects/boolobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ static PyNumberMethods bool_as_number = {
137137
bool_xor, /* nb_xor */
138138
bool_or, /* nb_or */
139139
0, /* nb_int */
140-
0, /* nb_reserved */
140+
0, /* nb_complex */
141141
0, /* nb_float */
142142
0, /* nb_inplace_add */
143143
0, /* nb_inplace_subtract */

Objects/clinic/complexobject.c.h

Lines changed: 1 addition & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)