Skip to content

Add nb_complex slot to PyNumberMethods (was nb_reserved) #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ sub-slots
+---------------------------------------------------------+-----------------------------------+---------------+
| :c:member:`~PyNumberMethods.nb_int` | :c:type:`unaryfunc` | __int__ |
+---------------------------------------------------------+-----------------------------------+---------------+
| :c:member:`~PyNumberMethods.nb_reserved` | void * | |
| :c:member:`~PyNumberMethods.nb_complex` | :c:type:`unaryfunc` | __complex__ |
+---------------------------------------------------------+-----------------------------------+---------------+
| :c:member:`~PyNumberMethods.nb_float` | :c:type:`unaryfunc` | __float__ |
+---------------------------------------------------------+-----------------------------------+---------------+
Expand Down Expand Up @@ -2304,7 +2304,7 @@ Number Object Structures
binaryfunc nb_xor;
binaryfunc nb_or;
unaryfunc nb_int;
void *nb_reserved;
unaryfunc nb_complex;
unaryfunc nb_float;

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

.. note::

The :c:member:`~PyNumberMethods.nb_reserved` field should always be ``NULL``. It
was previously called :c:member:`!nb_long`, and was renamed in
Python 3.0.1.

.. c:member:: binaryfunc PyNumberMethods.nb_add
.. c:member:: binaryfunc PyNumberMethods.nb_subtract
Expand All @@ -2361,7 +2356,7 @@ Number Object Structures
.. c:member:: binaryfunc PyNumberMethods.nb_xor
.. c:member:: binaryfunc PyNumberMethods.nb_or
.. c:member:: unaryfunc PyNumberMethods.nb_int
.. c:member:: void *PyNumberMethods.nb_reserved
.. c:member:: unaryfunc PyNumberMethods.nb_complex
.. c:member:: unaryfunc PyNumberMethods.nb_float
.. c:member:: binaryfunc PyNumberMethods.nb_inplace_add
.. c:member:: binaryfunc PyNumberMethods.nb_inplace_subtract
Expand Down
2 changes: 1 addition & 1 deletion Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ typedef struct {
binaryfunc nb_xor;
binaryfunc nb_or;
unaryfunc nb_int;
void *nb_reserved; /* the slot formerly known as nb_long */
unaryfunc nb_complex; /* the slot formerly known as nb_long */
unaryfunc nb_float;

binaryfunc nb_inplace_add;
Expand Down
4 changes: 4 additions & 0 deletions Include/typeslots.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,7 @@
/* New in 3.14 */
#define Py_tp_token 83
#endif
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030E0000
/* New in 3.14 */
#define Py_nb_complex 84
#endif
10 changes: 10 additions & 0 deletions Lib/test/test_capi/test_complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,16 @@ def test_py_c_abs(self):

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

def test_old__complex__(self):
old_complex_like = _testcapi.old_complex_like

x = old_complex_like()
with self.assertWarns(DeprecationWarning):
self.assertEqual(complex(x), 1+2j)
with warnings.catch_warnings():
warnings.simplefilter("error", DeprecationWarning)
self.assertRaises(DeprecationWarning, complex, x)


if __name__ == "__main__":
unittest.main()
4 changes: 4 additions & 0 deletions Lib/test/test_capi/test_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class IntLike(WithDunder):
class FloatLike(WithDunder):
methname = '__float__'

class ComplexLike(WithDunder):
methname = '__complex__'


def subclassof(base):
return type(base.__name__ + 'Subclass', (base,), {})
Expand All @@ -80,6 +83,7 @@ def test_check(self):
self.assertTrue(check(0.5))
self.assertTrue(check(FloatLike.with_val(4.25)))
self.assertTrue(check(1+2j))
self.assertTrue(check(ComplexLike.with_val(1+2j)))

self.assertFalse(check([]))
self.assertFalse(check("abc"))
Expand Down
2 changes: 2 additions & 0 deletions Misc/stable_abi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@
added = '3.2'
[const.Py_nb_float]
added = '3.2'
[const.Py_nb_complex]
added = '3.14'
[const.Py_nb_inplace_add]
added = '3.2'
[const.Py_nb_inplace_subtract]
Expand Down
2 changes: 1 addition & 1 deletion Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3057,7 +3057,7 @@ static PyNumberMethods delta_as_number = {
0, /*nb_xor*/
0, /*nb_or*/
0, /*nb_int*/
0, /*nb_reserved*/
0, /*nb_complex*/
0, /*nb_float*/
0, /*nb_inplace_add*/
0, /*nb_inplace_subtract*/
Expand Down
4 changes: 2 additions & 2 deletions Modules/_decimal/_decimal.c
Original file line number Diff line number Diff line change
Expand Up @@ -4841,7 +4841,7 @@ dec_ceil(PyObject *self, PyObject *Py_UNUSED(dummy))

/* __complex__ */
static PyObject *
dec_complex(PyObject *self, PyObject *Py_UNUSED(dummy))
dec_complex(PyObject *self)
{
PyObject *f;
double x;
Expand Down Expand Up @@ -5161,7 +5161,6 @@ static PyMethodDef dec_methods [] =
{ "__ceil__", dec_ceil, METH_NOARGS, NULL },
{ "__floor__", dec_floor, METH_NOARGS, NULL },
{ "__trunc__", dec_trunc, METH_NOARGS, NULL },
{ "__complex__", dec_complex, METH_NOARGS, NULL },
{ "__sizeof__", dec_sizeof, METH_NOARGS, NULL },

{ NULL, NULL, 1 }
Expand Down Expand Up @@ -5194,6 +5193,7 @@ static PyType_Slot dec_slots[] = {
{Py_nb_bool, nm_nonzero},
{Py_nb_int, nm_dec_as_long},
{Py_nb_float, PyDec_AsFloat},
{Py_nb_complex, dec_complex},
{Py_nb_floor_divide, nm_mpd_qdivint},
{Py_nb_true_divide, nm_mpd_qdiv},
{0, NULL},
Expand Down
28 changes: 27 additions & 1 deletion Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2684,7 +2684,7 @@ static PyNumberMethods matmulType_as_number = {
0, /* nb_xor */
0, /* nb_or */
0, /* nb_int */
0, /* nb_reserved */
0, /* nb_complex */
0, /* nb_float */
0, /* nb_inplace_add */
0, /* nb_inplace_subtract */
Expand Down Expand Up @@ -3175,6 +3175,28 @@ create_manual_heap_type(void)
return (PyObject *)type;
}

/* Complex-like type with a __complex__ method, instead of nb_complex slot,
to test deprecation. */

static PyObject *
complex_dunder(PyObject *self, PyObject *Py_UNUSED(dummy))
{
return PyComplex_FromDoubles(1, 2);
}

static PyMethodDef old_complex_methods[] = {
{"__complex__", complex_dunder, METH_NOARGS},
{NULL, NULL} /* sentinel */
};

PyTypeObject OldComplexLikeType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "old_complex_like",
.tp_new = PyType_GenericNew,
.tp_methods = old_complex_methods,
};


static struct PyModuleDef _testcapimodule = {
PyModuleDef_HEAD_INIT,
.m_name = "_testcapi",
Expand Down Expand Up @@ -3315,6 +3337,10 @@ PyInit__testcapi(void)
return NULL;
}

if (PyType_Ready(&OldComplexLikeType) < 0)
return NULL;
Py_INCREF(&OldComplexLikeType);
PyModule_AddObject(m, "old_complex_like", (PyObject *)&OldComplexLikeType);

/* Include tests from the _testcapi/ directory */
if (_PyTestCapi_Init_Vectorcall(m) < 0) {
Expand Down
2 changes: 1 addition & 1 deletion Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@ PyNumber_Check(PyObject *o)
if (o == NULL)
return 0;
PyNumberMethods *nb = Py_TYPE(o)->tp_as_number;
return nb && (nb->nb_index || nb->nb_int || nb->nb_float || PyComplex_Check(o));
return nb && (nb->nb_index || nb->nb_int || nb->nb_float || nb->nb_complex);
}

/* Binary operators */
Expand Down
2 changes: 1 addition & 1 deletion Objects/boolobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ static PyNumberMethods bool_as_number = {
bool_xor, /* nb_xor */
bool_or, /* nb_or */
0, /* nb_int */
0, /* nb_reserved */
0, /* nb_complex */
0, /* nb_float */
0, /* nb_inplace_add */
0, /* nb_inplace_subtract */
Expand Down
20 changes: 1 addition & 19 deletions Objects/clinic/complexobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 39 additions & 37 deletions Objects/complexobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -488,34 +488,45 @@ PyComplex_ImagAsDouble(PyObject *op)
static PyObject *
try_complex_special_method(PyObject *op)
{
PyObject *f;

f = _PyObject_LookupSpecial(op, &_Py_ID(__complex__));
if (f) {
PyObject *res = _PyObject_CallNoArgs(f);
Py_DECREF(f);
if (!res || PyComplex_CheckExact(res)) {
return res;
}
if (!PyComplex_Check(res)) {
PyErr_Format(PyExc_TypeError,
"__complex__ returned non-complex (type %.200s)",
Py_TYPE(res)->tp_name);
Py_DECREF(res);
return NULL;
}
/* Issue #29894: warn if 'res' not of exact type complex. */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"__complex__ returned non-complex (type %.200s). "
"The ability to return an instance of a strict subclass of complex "
"is deprecated, and may be removed in a future version of Python.",
Py_TYPE(res)->tp_name)) {
Py_DECREF(res);
return NULL;
PyNumberMethods *m = Py_TYPE(op)->tp_as_number;
PyObject *res = NULL;

if (m && m->nb_complex) {
res = m->nb_complex(op);
}
else {
PyObject *f = _PyObject_LookupSpecial(op, &_Py_ID(__complex__));
if (f) {
res = _PyObject_CallNoArgs(f);
Py_DECREF(f);
if (PyErr_WarnEx(PyExc_DeprecationWarning,
"Use nb_complex slot to implement __complex__", 1)) {
Py_XDECREF(res);
return NULL;
}
}
}

if (!res || PyComplex_CheckExact(res)) {
return res;
}
return NULL;
if (!PyComplex_Check(res)) {
PyErr_Format(PyExc_TypeError,
"__complex__ returned non-complex (type %.200s)",
Py_TYPE(res)->tp_name);
Py_DECREF(res);
return NULL;
}
/* Issue #29894: warn if 'res' not of exact type complex. */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"__complex__ returned non-complex (type %.200s). "
"The ability to return an instance of a strict subclass of complex "
"is deprecated, and may be removed in a future version of Python.",
Py_TYPE(res)->tp_name)) {
Py_DECREF(res);
return NULL;
}
return res;
}

Py_complex
Expand Down Expand Up @@ -908,25 +919,17 @@ complex___format___impl(PyComplexObject *self, PyObject *format_spec)
return _PyUnicodeWriter_Finish(&writer);
}

/*[clinic input]
complex.__complex__

Convert this value to exact type complex.
[clinic start generated code]*/

static PyObject *
complex___complex___impl(PyComplexObject *self)
/*[clinic end generated code: output=e6b35ba3d275dc9c input=3589ada9d27db854]*/
complex_complex(PyObject *self)
{
if (PyComplex_CheckExact(self)) {
return Py_NewRef(self);
}
else {
return PyComplex_FromCComplex(self->cval);
return PyComplex_FromCComplex(((PyComplexObject *)self)->cval);
}
}


static PyObject *
complex_from_string_inner(const char *s, Py_ssize_t len, void *type)
{
Expand Down Expand Up @@ -1328,7 +1331,6 @@ complex_from_number_impl(PyTypeObject *type, PyObject *number)
static PyMethodDef complex_methods[] = {
COMPLEX_FROM_NUMBER_METHODDEF
COMPLEX_CONJUGATE_METHODDEF
COMPLEX___COMPLEX___METHODDEF
COMPLEX___GETNEWARGS___METHODDEF
COMPLEX___FORMAT___METHODDEF
{NULL, NULL} /* sentinel */
Expand Down Expand Up @@ -1360,7 +1362,7 @@ static PyNumberMethods complex_as_number = {
0, /* nb_xor */
0, /* nb_or */
0, /* nb_int */
0, /* nb_reserved */
complex_complex, /* nb_complex */
0, /* nb_float */
0, /* nb_inplace_add */
0, /* nb_inplace_subtract */
Expand Down
2 changes: 1 addition & 1 deletion Objects/floatobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1836,7 +1836,7 @@ static PyNumberMethods float_as_number = {
0, /* nb_xor */
0, /* nb_or */
float___trunc___impl, /* nb_int */
0, /* nb_reserved */
0, /* nb_complex */
float_float, /* nb_float */
0, /* nb_inplace_add */
0, /* nb_inplace_subtract */
Expand Down
2 changes: 1 addition & 1 deletion Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -6592,7 +6592,7 @@ static PyNumberMethods long_as_number = {
long_xor, /*nb_xor*/
long_or, /*nb_or*/
long_long, /*nb_int*/
0, /*nb_reserved*/
0, /*nb_complex*/
long_float, /*nb_float*/
0, /* nb_inplace_add */
0, /* nb_inplace_subtract */
Expand Down
2 changes: 1 addition & 1 deletion Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -2118,7 +2118,7 @@ static PyNumberMethods none_as_number = {
0, /* nb_xor */
0, /* nb_or */
0, /* nb_int */
0, /* nb_reserved */
0, /* nb_complex */
0, /* nb_float */
0, /* nb_inplace_add */
0, /* nb_inplace_subtract */
Expand Down
2 changes: 1 addition & 1 deletion Objects/setobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2484,7 +2484,7 @@ static PyNumberMethods set_as_number = {
set_xor, /*nb_xor*/
set_or, /*nb_or*/
0, /*nb_int*/
0, /*nb_reserved*/
0, /*nb_complex*/
0, /*nb_float*/
0, /*nb_inplace_add*/
set_isub, /*nb_inplace_subtract*/
Expand Down
Loading
Loading